library(Seurat)
library(conos)
library(ggpubr)
library(tidyverse)
library(SingleCellExperiment)
library(chromVAR)
library(motifmatchr)
library(BSgenome.Hsapiens.UCSC.hg38)
# library(monocle3)
source("~/multiOmic_benchmark/utils.R")
source("~/multiOmic_benchmark/integrateBenchmark.R")
source("~/multiOmic_benchmark/preprocess/selectFeatures.R")

## Make output directory
outdir <- "~/multiOmic_benchmark/report/output/20191127_tcellTrajectory/"
ifelse(!dir.exists(outdir), dir.create(outdir), FALSE)

Based on the results of my benchmark, I set out to align expression and accessibility profiles from the F74 developing thymus dataset to detect changes in accessibility along pseudotime trajectories. While the benchmark was based on the task of label propagation, I here use the two most faithful methods (Seurat CCA and Conos) to achieve a common embedding of ATAC-seq and RNA-seq cells.

Load datasets.

rna.sce <- readRDS("~/my_data/F74_RNA_seurat_processed.RDS")
atac.sce <- readRDS("~/my_data/F74_ATAC_snapAtac_processed_bgmat.RDS")

## Re-normalize RNA data
seu.rna <- as.Seurat(rna.sce, counts = "counts")
seu.rna <- NormalizeData(seu.rna)
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
logcounts(rna.sce) <- seu.rna@assays$RNA@data

Filter genes with zero variance

rna.gene.var <- as.matrix(counts(rna.sce)) %>% rowVars()
atac.gene.var <- as.matrix(counts(atac.sce)) %>% rowVars()

rna.sce <- rna.sce[which(rna.gene.var > 0),]
atac.sce <- atac.sce[which(atac.gene.var > 0),]

rna.sce; atac.sce
class: SingleCellExperiment 
dim: 24510 8321 
metadata(0):
assays(3): counts cpm logcounts
rownames(24510): RP11-34P13.3 RP11-34P13.7 ... AC233755.1 AC240274.1
rowData names(0):
colnames(8321): AAACCTGAGTTCGATC_1 AAACCTGCAAGTTGTC_1 ... TTTGTCAAGCTGAACG_2 TTTGTCAGTATTAGCC_2
colData names(1): annotation
reducedDimNames(0):
spikeNames(0):
class: SingleCellExperiment 
dim: 31122 5793 
metadata(0):
assays(3): counts cpm logcounts
rownames(31122): A1BG A1BG-AS1 ... ZYX ZZEF1
rowData names(0):
colnames(5793): AAACGAAAGTGAACCG-1 AAACGAACATCGGCCA-1 ... TTTGTGTTCGATCGCG-1 TTTGTGTTCTGAGTAC-1
colData names(28): orig.ident nCount_ATAC ... nFeature_ACTIVITY ident
reducedDimNames(2): LSI UMAP
spikeNames(0):

Integration of T cells clusters

I re-run the integration based on the T cell subset. To select cells from the scATAC dataset, I take the SnapATAC clusters that best correspond to T-cells, based on label transfer.

tcells.sce.atac <- atac.sce[,which(as.numeric(atac.sce$seurat_clusters) %in% c(1:9))]

tcells.rna.ix <- which(rna.sce$annotation %in% c("DN","DP (Q)", "DP (P)", "SP (1)", "SP (2)"))
tcells.sce.rna <- rna.sce[,tcells.rna.ix]

tcells.sce.list <- list(RNA=tcells.sce.rna, ATAC=tcells.sce.atac)

## Make color palette 4 cell types
cell.types <- as.character(unique(tcells.sce.rna$annotation))
cell.type.pal <- brewer.pal(length(cell.types), "Set1") %>% rev() %>% setNames(cell.types)

Next, I select genes on which to perform integration. I take the union of the most variable features in the RNA dataset and the most covered features in the ATAC dataset

hcg.atac <- select_highlyCovered(tcells.sce.list$ATAC, frac_cells = 0.2)
hvg.rna <- select_highlyVariable(tcells.sce.list$RNA)
Calculating gene means
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating gene variance to mean ratios
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
seu.rna <- FindVariableFeatures(seu.rna, nfeatures = 2000,
                                # selection.method = "mvp", dispersion.cutoff=c(0.7, 100), mean.cutoff=c(0.02, 3)
                                )
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
hvg.rna <- VariableFeatures(seu.rna)

VariableFeaturePlot(seu.rna)

UpSetR::upset(UpSetR::fromList(list(HVG.RNA=hvg.rna, HCG.ATAC=hcg.atac)))

Remove cell cycle genes, that might interfere with pseudotime ordering

cell_cycle_genes <- read.table("~/annotations/cell_cycle_genes.tsv")$V1

integrate_features_union <- union(hvg.rna, hcg.atac)
integrate_features_union <- setdiff(integrate_features_union, cell_cycle_genes) 

## Select features in both datasets
integrate_features_union <- intersect(integrate_features_union, intersect(rownames(tcells.sce.list$ATAC), rownames(tcells.sce.list$RNA))) 

Visualize T cells in RNA dataset

tcells.seu.list <- map(tcells.sce.list, ~ as.Seurat(.x))
All keys should be one or more alphanumeric characters followed by an underscore '_', setting key to LSI_All keys should be one or more alphanumeric characters followed by an underscore '_', setting key to UMAP_
tcells.RNA.union <- tcells.seu.list$RNA
VariableFeatures(tcells.RNA.union) <- integrate_features_union
tcells.RNA.union <- ScaleData(tcells.RNA.union) %>% RunPCA() %>% RunUMAP(dims=1:40)
Centering and scaling data matrix

  |                                                                                                                      
  |                                                                                                                |   0%
  |                                                                                                                      
  |======================                                                                                          |  20%
  |                                                                                                                      
  |=============================================                                                                   |  40%
  |                                                                                                                      
  |===================================================================                                             |  60%
  |                                                                                                                      
  |==========================================================================================                      |  80%
  |                                                                                                                      
  |================================================================================================================| 100%
The following 176 features requested have zero variance (running reduction without them): TM4SF18, MEOX2, ASGR2, DMRT2, KIR2DL4, CACNA1B, FREM2, MYZAP, TOX3, PRIMA1, PPP2R2B, PTPRZ1, CMTM5, TREM2, CALB2, TMTC1, CDH6, PKHD1L1, ISLR2, KRT7, MYH8, DCX, CCSER1, MSLN, CDH19, EYA4, P2RY12, PLA2G2D, ADAMTS16, KCNJ5, PAPPA, QRFPR, ADGRG2, NFIA-AS2, LUZP2, NALCN, FABP7, TSPEAR-AS1, GPR1, CD300LF, A4GALT, AP000439.1, CEACAM3, SLITRK2, KIR3DL1, AC147651.1, MUC15, FFAR4, C11orf53, KLK11, WIF1, CFHR1, TMEM233, FOLR3, GRID2, GALNT15, FAM19A1, TAC1, PTCHD4, CCL11, ZNF385B, GABRA1, ADH1B, LINC01048, LMOD1, TNNT1, ACTL6B, SEMA3E, HOXD-AS2, NTF4, SLC35F1, MANEAL, FEV, RAB3C, SYT9, HMCN2, ZFHX4-AS1, PCSK9, MMP12, ABCB11, AC002066.1, ADAM23, ADCY5, ADGB, ADGRB1, ANKRD29, ATP2B2, C4orf45, CCDC33, CDH18, CDK15, CERS1, CFAP161, CLRN1-AS1, CNBD1, CNGB3, CNTN5, COL8A2, CTNND2, DCC, DSCAM, EPHA6, FGF12, FSTL5, GABBR2, GABRB1, GABRG3, GALNT13, GHR, GLIS3, GNG12-AS1, GNGT1, GRIN2B, GRM1, GRM8, HMCN1, HPSE2, HS3ST5, HS6ST3, IL1RAPL2, ITGBL1, KCNB1, KCNH7, LINC00639, LINC01169, LINC01170, LINC01182, LINC01317, LINC01505, LINGO1, LINGO2, LRRC4C, LRRTM4, LY75-CD302, MAP6, MLIP, MYRIP, NEBL, NKAIN2, NKAIN3, NRG1, OPCML, PCDH15, PCDHGA4, PDZRN4, PIK3C2G, PKNOX2, PNPLA1, POU6F2, PPFIA2, PRR5-ARHGAP8, PTPN5, PTPRT, RALYL, RERG, RGS7, RIMS1, RIN2, RYR3, SHANK2, SNTG1, SORCS3, STON1-GTF2A1L, SYN2, TENM2, THSD4, TRPC6, TSPEAR, TVP23C-CDRT4, UNC80, USH1C, VAX2, VWA3B, XKR4, ZBTB7C, ZNF804BPC_ 1 
Positive:  SATB1, PTPRC, MTSS1, APBB1IP, LYST, SH2D1A, CAMK4, LBH, FBLN5, LEF1 
       PLEKHG1, VOPP1, CD247, MXD1, TCF12, ARHGEF7, ALDH1A2, NFATC3, TBC1D19, SYTL3 
       ADAMTS17, ANO6, DAPK1, CALN1, THEMIS, PITPNM2, NLGN4X, MAPRE2, GALNT7, ZNF280D 
Negative:  ENO1, GSTP1, FABP5, TMSB10, SMS, PKM, NME4, VIM, RPL37A, YWHAQ 
       NCL, IGFBP2, CAPG, NDUFA12, TRDC, PGK1, PARVB, LDHB, ATOX1, SELL 
       CDC123, NUDC, IGLL1, UBE2N, FXYD2, GMPS, C20orf27, SLC25A39, ANXA1, C12orf75 
PC_ 2 
Positive:  RPL37A, EIF3H, LDHB, TBCA, IL32, SERGEF, SMPD3, ITGAE, AATF, FBLN5 
       NDUFA12, SOD1, DNAJC15, C12orf75, MRPL33, TMSB10, HNRNPC, CCDC57, SMCO4, GDI2 
       OLA1, ALDH1A2, COX7A2L, CST3, CYSTM1, ATOX1, GYPC, PDCD6, FABP5, RALY 
Negative:  MBNL1, ITPR2, PTPRC, MTRNR2L12, ADAM10, MSI2, CDK6, SELL, JCHAIN, RNF213 
       MME, TCF12, BPTF, BCL11B, NIPBL, MACF1, PIK3R1, HIVEP3, SOCS2, GALNT7 
       IKZF2, RUNX1, SPTBN1, PRRC2B, BCL2, DIAPH1, MYCBP2, SLC38A1, MBP, RUFY3 
PC_ 3 
Positive:  HLA-B, TOX2, COTL1, CTSW, KLRB1, CD40LG, SIRPG, GZMM, CLDN1, CD74 
       ITM2A, GBP2, BACH2, HPGD, PDE4D, TNFRSF1B, S100A10, CLEC2D, XCL1, GFOD1 
       CRTAM, MATK, DENND2D, PDCD1, GIMAP4, STAT1, ZNF683, CD226, HLA-A, TNFRSF25 
Negative:  TFDP2, JCHAIN, ATP6AP1L, DEFA6, MME, GALNT2, PCGF5, ADGRG1, GLIPR1, MSI2 
       NINL, CEP70, CDK6, PTPN2, FXYD2, TRDC, GSTP1, SELL, SOCS2, RGPD3 
       SMPD3, FABP5, LYST, SSBP2, PITPNM2, DLEU7, UBE2E1, NUCB2, PPP1R1C, BCL11A 
PC_ 4 
Positive:  SMPD3, C12orf75, TUBA1C, LCP1, HIST1H2AB, SMS, SMCO4, GMPS, FABP5, XPO1 
       IGFBP2, CCND3, TAF15, RGS3, SREBF2, YWHAQ, TMSB15A, ABCD3, NCL, PHIP 
       GNAS, EPB41L2, SYNE2, STAG2, CNTLN, VIM, NUP210, MCTP1, NUDC, NFATC3 
Negative:  JCHAIN, SELL, DEFA6, SOCS2, GLIPR1, ATP6AP1L, MME, RGPD3, XG, DPP4 
       IFI6, ENAM, GNAL, EVL, NEGR1, FRMPD2, EVA1A, PIK3CD, GALNT2, NDST3 
       MBP, RNF144A, FXYD2, COL1A2, SMIM24, NDFIP2, OXNAD1, ACTN1, LSP1, LINC00861 
PC_ 5 
Positive:  HPGD, BACH2, TOX2, ITM2A, GZMM, ST6GAL1, ARAP2, CD96, TGFBR2, LZTFL1 
       VIM, EVL, CD44, SATB1, IL2, TUSC3, CYTIP, PDE4D, MAD1L1, SLC2A3 
       ARHGAP31, GPR183, PGK1, ANKRD44, STK4, BCL2, GNG2, IKZF1, ICOS, ETS1 
Negative:  XCL1, TNFRSF9, CTSW, XCL2, GBP2, TNFRSF1B, S100A4, GNG4, NPW, CD74 
       HOPX, IGFBP4, CD151, SH3BGRL2, NFATC1, LYST, ATP9A, NR4A2, LINC01480, CLEC2D 
       MIR3142HG, LINC01281, PREX1, HDAC9, PHACTR1, MAP7, CST3, CXCR3, PITPNM2, PDCD1 
The default method for RunUMAP has changed from calling Python UMAP via reticulate to the R-native UWOT using the cosine metric
To use Python UMAP via reticulate, set umap.method to 'umap-learn' and metric to 'correlation'
This message will be shown once per session09:11:47 UMAP embedding parameters a = 0.9922 b = 1.112
09:11:47 Read 7101 rows and found 40 numeric columns
09:11:47 Using Annoy for neighbor search, n_neighbors = 30
09:11:47 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
09:11:48 Writing NN index file to temp file /tmp/RtmpIaaTXb/file9c876abf67
09:11:48 Searching Annoy index using 1 thread, search_k = 3000
09:11:51 Annoy recall = 100%
09:11:52 Commencing smooth kNN distance calibration using 1 thread
09:11:55 Initializing from normalized Laplacian + noise
09:11:55 Commencing optimization for 500 epochs, with 314138 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
09:12:14 Optimization finished
DimPlot(tcells.RNA.union, group.by = "annotation", label=TRUE) + ggtitle("RNA - feature union")
Using `as.character()` on a quosure is deprecated as of rlang 0.3.0.
Please use `as_label()` or `as_name()` instead.
This warning is displayed once per session.

Visualize markers

t.cell.markers <- list(known.markers = c("CD34", "IGLL1", "TRGC2", "TRDC", "PTCRA", "TRBC2", "TRAC", "CD4", "CD8A", "CD8B"),
                       chemokine.receptors = c("CCR9", "CCR7"),
                       tcr.activation = c("CD5", "CD27"),
                       proliferation=c("PCNA", "CDK1", "MKI67"),
                       cyclin.D = c("CCND2", "CCND3"),
                       recombination=c("RAG1", "RAG2"),
                       apoptosis=c("HRK","BMF", "TP53INP1"),
                       stage.markers = c("ST18", "HIVEP3", "RGPD3", "SMPD3", "AQP3", "RORC", "SATB1", "TOX2")
                       ) 
# FeaturePlot(tcells.RNA.ref, features = t.cell.markers$known.markers, cols = viridis::viridis(n=10))
FeaturePlot(tcells.RNA.union, features = t.cell.markers$known.markers, cols = viridis::viridis(n=10))

Visualize T cells in ATAC dataset

Colored by clusters called with SnapATAC

tcells.ATAC.union <- tcells.seu.list$ATAC
# tcells.ATAC.union <- NormalizeData(tcells.ATAC.union)
VariableFeatures(tcells.ATAC.union) <- integrate_features_union
tcells.ATAC.union <- RunLSI(tcells.ATAC.union, n=50, scale.max = NULL)
RunLSI is being moved to Signac. Equivalent functionality can be achieved via the Signac::RunTFIDF and Signac::RunSVD functions; for more information on Signac, please see https://github.com/timoast/SignacRunLSI is being moved to Signac. Equivalent functionality can be achieved via the Signac::RunTFIDF and Signac::RunSVD functions; for more information on Signac, please see https://github.com/timoast/SignacRunLSI is being moved to Signac. Equivalent functionality can be achieved via the Signac::RunTFIDF and Signac::RunSVD functions; for more information on Signac, please see https://github.com/timoast/SignacPerforming TF-IDF normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Running SVD on TF-IDF matrix
Scaling cell embeddings
Cannot add objects with duplicate keys (offending key: LSI_), setting key to 'lsi_'
tcells.ATAC.union <- RunUMAP(tcells.ATAC.union, reduction = "lsi", dims = 1:50)
10:10:05 UMAP embedding parameters a = 0.9922 b = 1.112
10:10:05 Read 4977 rows and found 50 numeric columns
10:10:05 Using Annoy for neighbor search, n_neighbors = 30
10:10:05 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
10:10:06 Writing NN index file to temp file /tmp/RtmpIaaTXb/file9c861ce1d48
10:10:06 Searching Annoy index using 1 thread, search_k = 3000
10:10:08 Annoy recall = 100%
10:10:11 Commencing smooth kNN distance calibration using 1 thread
10:10:14 Initializing from normalized Laplacian + noise
10:10:14 Commencing optimization for 500 epochs, with 172060 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
10:10:26 Optimization finished
Cannot add objects with duplicate keys (offending key: UMAP_), setting key to 'umap_'
DimPlot(tcells.ATAC.union, reduction = "umap", group.by = "seurat_clusters", label = TRUE) + ggtitle("ATAC gmat")

Run CCA

Makes imputed transcriptome profile for the ATAC-seq cells to allow co-embedding

sce.list <- tcells.sce.list
reference = "RNA"
query = "ATAC" 
seurat.list <- imap(sce.list, ~ as.Seurat(.x, assay=.y))
seurat.list <- imap(seurat.list, ~ RenameCells(.x, add.cell.id=.y))
## Scale data
seurat.list <- map(seurat.list, ~ ScaleData(.x))
## Calculate CCA anchors
transfer.anchors <- FindTransferAnchors(reference = seurat.list[[reference]], 
                                        query = seurat.list[[query]],
                                        features = integrate_features_union, 
                                        reduction = "cca")

## Impute expression profiles for ATAC cells (for all genes, not just integration features)
refdata <- GetAssayData(seurat.list$RNA, assay = "RNA", slot = "data")
imputation <- TransferData(anchorset = transfer.anchors, refdata = refdata, weight.reduction = seurat.list$ATAC[["LSI"]])

## Merge datasets and co-embed
seurat.list$ATAC[["RNA"]] <- imputation
coembed <- merge(x = seurat.list$RNA, y = seurat.list$ATAC)

coembed <- ScaleData(coembed, features = integrate_features_union, do.scale = FALSE)
coembed <- RunPCA(coembed, features = integrate_features_union, verbose = FALSE)
coembed <- RunUMAP(coembed, dims = 1:30)

coembed <- AddMetaData(coembed, metadata = ifelse(colnames(coembed) %in% colnames(seurat.list[[reference]]), reference, query), col.name = "tech")

Transfer labels on ATAC dataset

celltype.predictions <- TransferData(anchorset = transfer.anchors, 
                                     refdata = seurat.list[[reference]]$annotation, 
                                     weight.reduction = seurat.list$ATAC[["LSI"]])

coembed <- AddMetaData(coembed, metadata = celltype.predictions)
coembed@meta.data %<>%
  rownames_to_column() %>%
  dplyr::mutate(annotation=ifelse(is.na(predicted.id) , annotation, NA)) %>%
  column_to_rownames()

coembed@meta.data <-
  coembed@meta.data %>%
  rownames_to_column() %>%
  dplyr::mutate(annotation=ifelse(is.na(annotation) & prediction.score.max > 0.5, predicted.id, annotation)) %>%
  dplyr::mutate(annotation=ifelse(annotation=="SP (2)", NA, annotation)) %>%
  column_to_rownames()
CombinePlots(
  list(DimPlot(coembed, group.by = c("predicted.id"), cols = cell.type.pal) + ggtitle("prediction"),
  DimPlot(coembed, group.by = c("annotation"), cols = cell.type.pal) + ggtitle("Original + prediction")),
  legend = "top"
  )

FeaturePlot(coembed, features = "prediction.score.max", cells = which(coembed$tech=="ATAC")) + scale_color_viridis_c()
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.

Run Pseudotime analysis

Identify cell of origin among the DN cells based on expression of IGLL1 and CD34

FeaturePlot(coembed, features = c("IGLL1", "CD34"), split.by = "tech", slot = "data", cols = viridis::viridis(n=100))

cell.oo <-
  coembed@meta.data %>% 
  rownames_to_column("cell") %>%
  mutate(IGLL1=coembed@assays$RNA@counts["IGLL1",cell]) %>%
  select(cell, annotation, IGLL1) %>%
  arrange(-IGLL1) %>%
  filter(annotation=="DN") %>%
  top_n(1, IGLL1) %>%
  pull(cell)

coembed@reductions$umap@cell.embeddings %>%
  as.tibble(rownames="cell") %>%
  mutate(cell.oo = ifelse(cell %in% cell.oo, T, F)) %>%
  ggplot(aes(UMAP_1, UMAP_2)) +
  geom_point(color="grey50") +
  geom_point(data=. %>% filter(cell.oo),color='red') +
  ggrepel::geom_text_repel(data=. %>% filter(cell.oo), aes(label="cell of origin"), color='red') +
  theme_cowplot() 

coembed <- AddMetaData(coembed, ifelse(colnames(coembed)==cell.oo, TRUE, FALSE), col.name = "iroot_cell")
merged.sce <- SingleCellExperiment(list(counts=coembed@assays$RNA@counts, logcounts=coembed@assays$RNA@data), colData=coembed@meta.data[, c("annotation", "tech", "iroot_cell")],
                     reducedDims = map(coembed@reductions, ~ .x@cell.embeddings))

saveRDS(object = merged.sce, "~/my_data/Tcells_CCA_integration_20191203.RDS")
saveRDS(object = integrate_features_union, "~/my_data/intFeatures_Tcells_CCA_integration_20191203.RDS")

I infer pseudotime using the diffusion pseudotime algorithm as implemented in scanpy. Making an R/reticulate wrapper for this function would be nice, but for now, see multiOmic_benchmark/DPT_tcells.ipynb.

Read scanpy output and save in R object.

dpt <- read.csv('~/my_data/Tcells_CCA_integration_20191127_scanpy_dpt.csv') %>%
  select(X, dpt_pseudotime)

coembed <- AddMetaData(coembed, column_to_rownames(dpt, 'X'))
saveRDS(coembed, "~/my_data/Tcells_CCA_integration_seurat_20191203.Rmd")
coembed <- readRDS("~/my_data/Tcells_CCA_integration_seurat_20191203.Rmd")

Visualize pseudotime

FeaturePlot(coembed, reduction = "umap", feature = "dpt_pseudotime", split.by = "tech", col=viridis::viridis(10)) 

Save figure

Check expression of markers along pseudotime

coembed@assays$RNA@data[t.cell.markers$known.markers, ] %>%
  as.matrix() %>%
  reshape2::melt(varnames=c("gene", "cell")) %>%
  left_join(coembed@meta.data[,"dpt_pseudotime", drop=F] %>% rownames_to_column("cell")) %>%
  mutate(pseudotime.rank=dense_rank(dpt_pseudotime)) %>%
  group_by(gene) %>%
  arrange(pseudotime.rank) %>%
  # mutate(value=(value-min(value))/max(value)-min(value)) %>%
  mutate(value=zoo::rollmean(value, k=5, fill=NA)) %>% 
  # mutate(value=(value-mean(value))/sd(value)) %>%
  ungroup() %>%
  mutate(gene=factor(gene, levels=rev(unique(gene)))) %>%
  ggplot(aes(pseudotime.rank, gene, fill=value)) +
  geom_tile() +
  scale_fill_viridis_c(name="log expression") +
  theme_bw(base_size = 16) +
  theme(panel.border = element_blank(), panel.grid = element_blank())

Bin pseudotime and visualize cell type composition

dpt.df <- 
  coembed@meta.data %>%
  rownames_to_column("cell") %>%
  dplyr::mutate(dpt_rank=dense_rank(dpt_pseudotime)) %>%
  mutate(dpt_bin=cut(dpt_rank, breaks = 100)) %>%
  mutate(dpt_bin=as.numeric(dpt_bin)) %>%
  select(cell,tech, annotation, prediction.score.max, dpt_bin, dpt_pseudotime, dpt_rank)

cell.type.pl <- dpt.df %>%
  ggplot(aes(dpt_bin, fill = annotation)) +
  # geom_histogram(bins=100) +
  geom_bar() +
  scale_fill_manual(values=cell.type.pal, na.value="grey50") +
  facet_grid(tech~., scales="free_y") +
  xlab("Pseudotime bin") +
  theme_bw(base_size = 16)

cell.type.pl

Correlation between global accessibility and pseudotime ordering.

snap.out <- readRDS(file = "~/my_data/cellranger-atac110_count_30439_WSSS8038360_GRCh38-1_1_0.snapATAC.RDS")
# atac.dpt.df <- 
#   coembed@meta.data %>%
#   rownames_to_column("cell") %>%
#   filter(tech=="ATAC") %>%
#   # group_by(tech) %>%
#   dplyr::mutate(dpt_rank=dense_rank(dpt_pseudotime)) %>%
#   mutate(dpt_bin=cut(dpt_rank, breaks = 100)) %>%
#   mutate(dpt_bin=as.numeric(dpt_bin)) %>%
#   # ungroup() %>%
#   select(cell,tech, annotation, prediction.score.max, dpt_bin, dpt_pseudotime)

groups <- dpt.df[dpt.df$tech=="ATAC", c("cell", "dpt_bin")]
bmat <- snap.out@bmat[str_remove(groups$cell, "ATAC_"),]
frac.accessible <- rowSums(bmat)/ncol(bmat)
acc.fraction.pl <- groups %>%
  mutate(frac_accessible=frac.accessible[str_remove(cell, "ATAC_")]) %>%
  ggplot(aes(dpt_bin, frac_accessible)) +
  geom_boxplot(aes(group=as.factor(dpt_bin)), outlier.alpha = 0.3, outlier.size = 0.7) +
  # geom_jitter(alpha=0.1) +
  xlab("Pseudotime bin") +
  ylab("Fraction of accessible bins") +
  facet_grid('ATAC'~.) +
  theme_bw(base_size = 16) 
  
dpt.pl <- plot_grid(cell.type.pl + theme(legend.position="top", axis.text.x = element_blank(), axis.ticks.x = element_blank(), axis.title.x = element_blank()), 
          acc.fraction.pl, 
          align = "v", ncol=1, nrow=2, axis="l")

dpt.pl +
  ggsave(paste0(outdir, "DPT_bins.png"), width=10, height = 7)

Motif analysis

I initially wanted to call peaks from SnapATAC clusters, then build a cell x peak matrix on those detected peaks, but SnapATAC/MACS2 don’t seem to work.

Alternative: load peak matrix from cellranger and add to snap object

filt.peaks <- Read10X_h5("~/my_data/filtered_peak_bc_matrix.h5")
peaks.mat <- str_split(rownames(filt.peaks), pattern = ":|-") %>% map(rbind) %>% purrr::reduce(rbind)
peaks.gr <- GRanges(peaks.mat[,1], IRanges(as.numeric(peaks.mat[,2]), as.numeric(peaks.mat[,3])))
snap.pmat <- createSnapFromPmat(mat=t(filt.peaks[,snap.out@barcode]), barcodes=snap.out@barcode, peaks=peaks.gr)
snap.pmat

Calculating deviations in TF accessibility using ChromVAR. This is a measure of how much is motif accessibility in each cell is enriched compared to all the cells and general cell coverage. While SnapATAC has an wrapper around ChromVAR that outputs the deviation matrix, I just take the code from that function and run every step separately to keep the useful outputs and statistics of chromVAR.

snap.pmat = makeBinary(snap.pmat, "pmat")

obj = snap.pmat
input.mat="pmat"
min.count=10
species="Homo sapiens"
genome=BSgenome.Hsapiens.UCSC.hg38

data.use = obj@pmat
peak.use = obj@peak

ncell = nrow(data.use)

idy = which(Matrix::colSums(data.use) >= min.count)
data.use = data.use[,idy,dropping=TRUE]
    
peak.use = peak.use[idy]

rse <- SummarizedExperiment(
        assays = list(counts = t(data.use)), 
                 rowRanges = peak.use, 
                 colData = DataFrame(Cell_Type=1:nrow(data.use), depth=Matrix::rowSums(data.use))
    );
rse <- addGCBias(rse, genome = genome);
motifs <- getJasparMotifs(collection = "CORE", species=species);
motif_mm <- matchMotifs(motifs, rse, genome = genome);
dev <- computeDeviations(object = rse, annotations = motif_mm);
var <- computeVariability(dev)

Save

rowData(dev) %<>%
  as.tibble(rownames="motif") %>%
  full_join(var) %>%
  column_to_rownames('motif') %>%
  DataFrame()

saveRDS(dev, "~/my_data/Tcells_peaks/Tcells_chromVarOutput.RDS")  

Visualize deviation scores of the most variable motifs, ordered in pseudotime.

dpt.order <-
  dpt.df %>%
  filter(tech=="RNA") %>%
  arrange(dpt_pseudotime) 

coembed <- ScaleData(coembed, do.scale=TRUE)
Centering and scaling data matrix

  |                                                                                                                      
  |                                                                                                                |   0%
  |                                                                                                                      
  |====                                                                                                            |   4%
  |                                                                                                                      
  |=========                                                                                                       |   8%
  |                                                                                                                      
  |=============                                                                                                   |  12%
  |                                                                                                                      
  |==================                                                                                              |  16%
  |                                                                                                                      
  |======================                                                                                          |  20%
  |                                                                                                                      
  |===========================                                                                                     |  24%
  |                                                                                                                      
  |===============================                                                                                 |  28%
  |                                                                                                                      
  |====================================                                                                            |  32%
  |                                                                                                                      
  |========================================                                                                        |  36%
  |                                                                                                                      
  |=============================================                                                                   |  40%
  |                                                                                                                      
  |=================================================                                                               |  44%
  |                                                                                                                      
  |======================================================                                                          |  48%
  |                                                                                                                      
  |==========================================================                                                      |  52%
  |                                                                                                                      
  |===============================================================                                                 |  56%
  |                                                                                                                      
  |===================================================================                                             |  60%
  |                                                                                                                      
  |========================================================================                                        |  64%
  |                                                                                                                      
  |============================================================================                                    |  68%
  |                                                                                                                      
  |=================================================================================                               |  72%
  |                                                                                                                      
  |=====================================================================================                           |  76%
  |                                                                                                                      
  |==========================================================================================                      |  80%
  |                                                                                                                      
  |==============================================================================================                  |  84%
  |                                                                                                                      
  |===================================================================================================             |  88%
  |                                                                                                                      
  |=======================================================================================================         |  92%
  |                                                                                                                      
  |============================================================================================================    |  96%
  |                                                                                                                      
  |================================================================================================================| 100%
gexmat.topvar <- coembed@assays$RNA@scale.data[tf.topvar[which(tf.topvar %in% rownames(coembed@assays$RNA@scale.data))],dpt.order$cell]
smooth.gexmat <- apply(gexmat.topvar, 1, function(x) zoo::rollmean(x, k=30)) %>% t() 
smooth.gexmat %>%
  # t() %>% scale() %>% t() %>%
  pheatmap::pheatmap(show_colnames = F, cluster_rows = T, cluster_cols = F, 
                     annotation_col = dpt.order[,c("cell", "annotation", "dpt_pseudotime")] %>% column_to_rownames("cell"),
                     annotation_colors = list(annotation=cell.type.pal, dpt_pseudotime=viridis::viridis(100)), fontsize = 18, fontsize_row = 12,
                     breaks=seq(-2,2, length.out = 100)
  )

Compare motif accessibility trend with gene expression trend along pseudotime. I find both examples of correlation between accessibility and TF expression (e.g. RUNX2, ELK3) and anti-correlation (e.g. JUN, ETV6).

map(list("JUN", "ELK3", "RUNX2", "REL", "FOS", "ETV6", "TCF3"), ~ plot.tfs(.x) + ggsave(paste0(outdir, paste0('TF_plot_',.x,".png")), width = 8, height=5))
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding factor and character vector, coercing into character vectorbinding character and factor vector, coercing into character vector
[[1]]

[[2]]

[[3]]

[[4]]

[[5]]

[[6]]

[[7]]

–>

–> –> –> –> –> –> –> –>

–> –> –> –> –> –> –>

–> –> –> –> –>

–> –> –> –>

–> –> –>


LS0tCnRpdGxlOiAiUHNldWRvdGltZSBhbmFseXNpcyBvZiBULWNlbGxzIGluIGRldmVsb3BpbmcgdGh5bXVzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7cn0KbGlicmFyeShTZXVyYXQpCmxpYnJhcnkoY29ub3MpCmxpYnJhcnkoZ2dwdWJyKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShTaW5nbGVDZWxsRXhwZXJpbWVudCkKbGlicmFyeShjaHJvbVZBUikKbGlicmFyeShtb3RpZm1hdGNocikKbGlicmFyeShCU2dlbm9tZS5Ic2FwaWVucy5VQ1NDLmhnMzgpCiMgbGlicmFyeShtb25vY2xlMykKc291cmNlKCJ+L211bHRpT21pY19iZW5jaG1hcmsvdXRpbHMuUiIpCnNvdXJjZSgifi9tdWx0aU9taWNfYmVuY2htYXJrL2ludGVncmF0ZUJlbmNobWFyay5SIikKc291cmNlKCJ+L211bHRpT21pY19iZW5jaG1hcmsvcHJlcHJvY2Vzcy9zZWxlY3RGZWF0dXJlcy5SIikKCiMjIE1ha2Ugb3V0cHV0IGRpcmVjdG9yeQpvdXRkaXIgPC0gIn4vbXVsdGlPbWljX2JlbmNobWFyay9yZXBvcnQvb3V0cHV0LzIwMTkxMTI3X3RjZWxsVHJhamVjdG9yeS8iCmlmZWxzZSghZGlyLmV4aXN0cyhvdXRkaXIpLCBkaXIuY3JlYXRlKG91dGRpciksIEZBTFNFKQpgYGAKIAogCkJhc2VkIG9uIHRoZSByZXN1bHRzIG9mIG15IGJlbmNobWFyaywgSSBzZXQgb3V0IHRvIGFsaWduIGV4cHJlc3Npb24gYW5kIGFjY2Vzc2liaWxpdHkgcHJvZmlsZXMgZnJvbSB0aGUgRjc0IGRldmVsb3BpbmcgdGh5bXVzIGRhdGFzZXQgdG8gZGV0ZWN0IGNoYW5nZXMgaW4gYWNjZXNzaWJpbGl0eSBhbG9uZyBwc2V1ZG90aW1lIHRyYWplY3Rvcmllcy4gV2hpbGUgdGhlIGJlbmNobWFyayB3YXMgYmFzZWQgb24gdGhlIHRhc2sgb2YgbGFiZWwgcHJvcGFnYXRpb24sIEkgaGVyZSB1c2UgdGhlIHR3byBtb3N0IGZhaXRoZnVsIG1ldGhvZHMgKFNldXJhdCBDQ0EgYW5kIENvbm9zKSB0byBhY2hpZXZlIGEgY29tbW9uIGVtYmVkZGluZyBvZiBBVEFDLXNlcSBhbmQgUk5BLXNlcSBjZWxscy4KCkxvYWQgZGF0YXNldHMuCgpgYGB7cn0Kcm5hLnNjZSA8LSByZWFkUkRTKCJ+L215X2RhdGEvRjc0X1JOQV9zZXVyYXRfcHJvY2Vzc2VkLlJEUyIpCmF0YWMuc2NlIDwtIHJlYWRSRFMoIn4vbXlfZGF0YS9GNzRfQVRBQ19zbmFwQXRhY19wcm9jZXNzZWRfYmdtYXQuUkRTIikKCiMjIFJlLW5vcm1hbGl6ZSBSTkEgZGF0YQpzZXUucm5hIDwtIGFzLlNldXJhdChybmEuc2NlLCBjb3VudHMgPSAiY291bnRzIikKc2V1LnJuYSA8LSBOb3JtYWxpemVEYXRhKHNldS5ybmEpCmxvZ2NvdW50cyhybmEuc2NlKSA8LSBzZXUucm5hQGFzc2F5cyRSTkFAZGF0YQoKYGBgCgpGaWx0ZXIgZ2VuZXMgd2l0aCB6ZXJvIHZhcmlhbmNlCmBgYHtyfQpybmEuZ2VuZS52YXIgPC0gYXMubWF0cml4KGNvdW50cyhybmEuc2NlKSkgJT4lIHJvd1ZhcnMoKQphdGFjLmdlbmUudmFyIDwtIGFzLm1hdHJpeChjb3VudHMoYXRhYy5zY2UpKSAlPiUgcm93VmFycygpCgpybmEuc2NlIDwtIHJuYS5zY2Vbd2hpY2gocm5hLmdlbmUudmFyID4gMCksXQphdGFjLnNjZSA8LSBhdGFjLnNjZVt3aGljaChhdGFjLmdlbmUudmFyID4gMCksXQoKcm5hLnNjZTsgYXRhYy5zY2UKYGBgCgoKIyMgSW50ZWdyYXRpb24gb2YgVCBjZWxscyBjbHVzdGVycwpJIHJlLXJ1biB0aGUgaW50ZWdyYXRpb24gYmFzZWQgb24gdGhlIFQgY2VsbCBzdWJzZXQuIFRvIHNlbGVjdCBjZWxscyBmcm9tIHRoZSBzY0FUQUMgZGF0YXNldCwgSSB0YWtlIHRoZSBTbmFwQVRBQyBjbHVzdGVycyB0aGF0IGJlc3QgY29ycmVzcG9uZCB0byBULWNlbGxzLCBiYXNlZCBvbiBsYWJlbCB0cmFuc2Zlci4KCmBgYHtyfQp0Y2VsbHMuc2NlLmF0YWMgPC0gYXRhYy5zY2VbLHdoaWNoKGFzLm51bWVyaWMoYXRhYy5zY2Ukc2V1cmF0X2NsdXN0ZXJzKSAlaW4lIGMoMTo5KSldCgp0Y2VsbHMucm5hLml4IDwtIHdoaWNoKHJuYS5zY2UkYW5ub3RhdGlvbiAlaW4lIGMoIkROIiwiRFAgKFEpIiwgIkRQIChQKSIsICJTUCAoMSkiLCAiU1AgKDIpIikpCnRjZWxscy5zY2Uucm5hIDwtIHJuYS5zY2VbLHRjZWxscy5ybmEuaXhdCgp0Y2VsbHMuc2NlLmxpc3QgPC0gbGlzdChSTkE9dGNlbGxzLnNjZS5ybmEsIEFUQUM9dGNlbGxzLnNjZS5hdGFjKQoKIyMgTWFrZSBjb2xvciBwYWxldHRlIDQgY2VsbCB0eXBlcwpjZWxsLnR5cGVzIDwtIGFzLmNoYXJhY3Rlcih1bmlxdWUodGNlbGxzLnNjZS5ybmEkYW5ub3RhdGlvbikpCmNlbGwudHlwZS5wYWwgPC0gYnJld2VyLnBhbChsZW5ndGgoY2VsbC50eXBlcyksICJTZXQxIikgJT4lIHJldigpICU+JSBzZXROYW1lcyhjZWxsLnR5cGVzKQpgYGAKCk5leHQsIEkgc2VsZWN0IGdlbmVzIG9uIHdoaWNoIHRvIHBlcmZvcm0gaW50ZWdyYXRpb24uIEkgdGFrZSB0aGUgdW5pb24gb2YgdGhlIG1vc3QgdmFyaWFibGUgZmVhdHVyZXMgaW4gdGhlIFJOQSBkYXRhc2V0IGFuZCB0aGUgbW9zdCBjb3ZlcmVkIGZlYXR1cmVzIGluIHRoZSBBVEFDIGRhdGFzZXQKCmBgYHtyfQpoY2cuYXRhYyA8LSBzZWxlY3RfaGlnaGx5Q292ZXJlZCh0Y2VsbHMuc2NlLmxpc3QkQVRBQywgZnJhY19jZWxscyA9IDAuMikKaHZnLnJuYSA8LSBzZWxlY3RfaGlnaGx5VmFyaWFibGUodGNlbGxzLnNjZS5saXN0JFJOQSkKCnNldS5ybmEgPC0gRmluZFZhcmlhYmxlRmVhdHVyZXMoc2V1LnJuYSwgbmZlYXR1cmVzID0gMjAwMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHNlbGVjdGlvbi5tZXRob2QgPSAibXZwIiwgZGlzcGVyc2lvbi5jdXRvZmY9YygwLjcsIDEwMCksIG1lYW4uY3V0b2ZmPWMoMC4wMiwgMykKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCmh2Zy5ybmEgPC0gVmFyaWFibGVGZWF0dXJlcyhzZXUucm5hKQoKVmFyaWFibGVGZWF0dXJlUGxvdChzZXUucm5hKQpVcFNldFI6OnVwc2V0KFVwU2V0Ujo6ZnJvbUxpc3QobGlzdChIVkcuUk5BPWh2Zy5ybmEsIEhDRy5BVEFDPWhjZy5hdGFjKSkpCmBgYAoKUmVtb3ZlIGNlbGwgY3ljbGUgZ2VuZXMsIHRoYXQgbWlnaHQgaW50ZXJmZXJlIHdpdGggcHNldWRvdGltZSBvcmRlcmluZwpgYGB7cn0KY2VsbF9jeWNsZV9nZW5lcyA8LSByZWFkLnRhYmxlKCJ+L2Fubm90YXRpb25zL2NlbGxfY3ljbGVfZ2VuZXMudHN2IikkVjEKCmludGVncmF0ZV9mZWF0dXJlc191bmlvbiA8LSB1bmlvbihodmcucm5hLCBoY2cuYXRhYykKaW50ZWdyYXRlX2ZlYXR1cmVzX3VuaW9uIDwtIHNldGRpZmYoaW50ZWdyYXRlX2ZlYXR1cmVzX3VuaW9uLCBjZWxsX2N5Y2xlX2dlbmVzKSAKCiMjIFNlbGVjdCBmZWF0dXJlcyBpbiBib3RoIGRhdGFzZXRzCmludGVncmF0ZV9mZWF0dXJlc191bmlvbiA8LSBpbnRlcnNlY3QoaW50ZWdyYXRlX2ZlYXR1cmVzX3VuaW9uLCBpbnRlcnNlY3Qocm93bmFtZXModGNlbGxzLnNjZS5saXN0JEFUQUMpLCByb3duYW1lcyh0Y2VsbHMuc2NlLmxpc3QkUk5BKSkpIAoKYGBgCgojIyMjIFZpc3VhbGl6ZSBUIGNlbGxzIGluIFJOQSBkYXRhc2V0CmBgYHtyfQp0Y2VsbHMuc2V1Lmxpc3QgPC0gbWFwKHRjZWxscy5zY2UubGlzdCwgfiBhcy5TZXVyYXQoLngpKQp0Y2VsbHMuUk5BLnVuaW9uIDwtIHRjZWxscy5zZXUubGlzdCRSTkEKVmFyaWFibGVGZWF0dXJlcyh0Y2VsbHMuUk5BLnVuaW9uKSA8LSBpbnRlZ3JhdGVfZmVhdHVyZXNfdW5pb24KdGNlbGxzLlJOQS51bmlvbiA8LSBTY2FsZURhdGEodGNlbGxzLlJOQS51bmlvbikgJT4lIFJ1blBDQSgpICU+JSBSdW5VTUFQKGRpbXM9MTo0MCkKCkRpbVBsb3QodGNlbGxzLlJOQS51bmlvbiwgZ3JvdXAuYnkgPSAiYW5ub3RhdGlvbiIsIGxhYmVsPVRSVUUpICsgZ2d0aXRsZSgiUk5BIC0gZmVhdHVyZSB1bmlvbiIpCmBgYAoKVmlzdWFsaXplIG1hcmtlcnMgCmBgYHtyLCBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9MTB9CnQuY2VsbC5tYXJrZXJzIDwtIGxpc3Qoa25vd24ubWFya2VycyA9IGMoIkNEMzQiLCAiSUdMTDEiLCAiVFJHQzIiLCAiVFJEQyIsICJQVENSQSIsICJUUkJDMiIsICJUUkFDIiwgIkNENCIsICJDRDhBIiwgIkNEOEIiKSwKICAgICAgICAgICAgICAgICAgICAgICBjaGVtb2tpbmUucmVjZXB0b3JzID0gYygiQ0NSOSIsICJDQ1I3IiksCiAgICAgICAgICAgICAgICAgICAgICAgdGNyLmFjdGl2YXRpb24gPSBjKCJDRDUiLCAiQ0QyNyIpLAogICAgICAgICAgICAgICAgICAgICAgIHByb2xpZmVyYXRpb249YygiUENOQSIsICJDREsxIiwgIk1LSTY3IiksCiAgICAgICAgICAgICAgICAgICAgICAgY3ljbGluLkQgPSBjKCJDQ05EMiIsICJDQ05EMyIpLAogICAgICAgICAgICAgICAgICAgICAgIHJlY29tYmluYXRpb249YygiUkFHMSIsICJSQUcyIiksCiAgICAgICAgICAgICAgICAgICAgICAgYXBvcHRvc2lzPWMoIkhSSyIsIkJNRiIsICJUUDUzSU5QMSIpLAogICAgICAgICAgICAgICAgICAgICAgIHN0YWdlLm1hcmtlcnMgPSBjKCJTVDE4IiwgIkhJVkVQMyIsICJSR1BEMyIsICJTTVBEMyIsICJBUVAzIiwgIlJPUkMiLCAiU0FUQjEiLCAiVE9YMiIpCiAgICAgICAgICAgICAgICAgICAgICAgKSAKIyBGZWF0dXJlUGxvdCh0Y2VsbHMuUk5BLnJlZiwgZmVhdHVyZXMgPSB0LmNlbGwubWFya2VycyRrbm93bi5tYXJrZXJzLCBjb2xzID0gdmlyaWRpczo6dmlyaWRpcyhuPTEwKSkKRmVhdHVyZVBsb3QodGNlbGxzLlJOQS51bmlvbiwgZmVhdHVyZXMgPSB0LmNlbGwubWFya2VycyRrbm93bi5tYXJrZXJzLCBjb2xzID0gdmlyaWRpczo6dmlyaWRpcyhuPTEwKSkKYGBgCgojIyMjIFZpc3VhbGl6ZSBUIGNlbGxzIGluIEFUQUMgZGF0YXNldAoKQ29sb3JlZCBieSBjbHVzdGVycyBjYWxsZWQgd2l0aCBTbmFwQVRBQwoKYGBge3J9CnRjZWxscy5BVEFDLnVuaW9uIDwtIHRjZWxscy5zZXUubGlzdCRBVEFDCiMgdGNlbGxzLkFUQUMudW5pb24gPC0gTm9ybWFsaXplRGF0YSh0Y2VsbHMuQVRBQy51bmlvbikKVmFyaWFibGVGZWF0dXJlcyh0Y2VsbHMuQVRBQy51bmlvbikgPC0gaW50ZWdyYXRlX2ZlYXR1cmVzX3VuaW9uCnRjZWxscy5BVEFDLnVuaW9uIDwtIFJ1bkxTSSh0Y2VsbHMuQVRBQy51bmlvbiwgbj01MCwgc2NhbGUubWF4ID0gTlVMTCkKdGNlbGxzLkFUQUMudW5pb24gPC0gUnVuVU1BUCh0Y2VsbHMuQVRBQy51bmlvbiwgcmVkdWN0aW9uID0gImxzaSIsIGRpbXMgPSAxOjUwKQoKRGltUGxvdCh0Y2VsbHMuQVRBQy51bmlvbiwgcmVkdWN0aW9uID0gInVtYXAiLCBncm91cC5ieSA9ICJzZXVyYXRfY2x1c3RlcnMiLCBsYWJlbCA9IFRSVUUpICsgZ2d0aXRsZSgiQVRBQyBnbWF0IikKYGBgCgojIyMjIFJ1biBDQ0EgCgpNYWtlcyBpbXB1dGVkIHRyYW5zY3JpcHRvbWUgcHJvZmlsZSBmb3IgdGhlIEFUQUMtc2VxIGNlbGxzIHRvIGFsbG93IGNvLWVtYmVkZGluZwoKYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD01LCBldmFsPUZBTFNFfQpzY2UubGlzdCA8LSB0Y2VsbHMuc2NlLmxpc3QKcmVmZXJlbmNlID0gIlJOQSIKcXVlcnkgPSAiQVRBQyIgCnNldXJhdC5saXN0IDwtIGltYXAoc2NlLmxpc3QsIH4gYXMuU2V1cmF0KC54LCBhc3NheT0ueSkpCnNldXJhdC5saXN0IDwtIGltYXAoc2V1cmF0Lmxpc3QsIH4gUmVuYW1lQ2VsbHMoLngsIGFkZC5jZWxsLmlkPS55KSkKIyMgU2NhbGUgZGF0YQpzZXVyYXQubGlzdCA8LSBtYXAoc2V1cmF0Lmxpc3QsIH4gU2NhbGVEYXRhKC54KSkKIyMgQ2FsY3VsYXRlIENDQSBhbmNob3JzCnRyYW5zZmVyLmFuY2hvcnMgPC0gRmluZFRyYW5zZmVyQW5jaG9ycyhyZWZlcmVuY2UgPSBzZXVyYXQubGlzdFtbcmVmZXJlbmNlXV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlcnkgPSBzZXVyYXQubGlzdFtbcXVlcnldXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gaW50ZWdyYXRlX2ZlYXR1cmVzX3VuaW9uLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9ICJjY2EiKQoKIyMgSW1wdXRlIGV4cHJlc3Npb24gcHJvZmlsZXMgZm9yIEFUQUMgY2VsbHMgKGZvciBhbGwgZ2VuZXMsIG5vdCBqdXN0IGludGVncmF0aW9uIGZlYXR1cmVzKQpyZWZkYXRhIDwtIEdldEFzc2F5RGF0YShzZXVyYXQubGlzdCRSTkEsIGFzc2F5ID0gIlJOQSIsIHNsb3QgPSAiZGF0YSIpCmltcHV0YXRpb24gPC0gVHJhbnNmZXJEYXRhKGFuY2hvcnNldCA9IHRyYW5zZmVyLmFuY2hvcnMsIHJlZmRhdGEgPSByZWZkYXRhLCB3ZWlnaHQucmVkdWN0aW9uID0gc2V1cmF0Lmxpc3QkQVRBQ1tbIkxTSSJdXSkKCiMjIE1lcmdlIGRhdGFzZXRzIGFuZCBjby1lbWJlZApzZXVyYXQubGlzdCRBVEFDW1siUk5BIl1dIDwtIGltcHV0YXRpb24KY29lbWJlZCA8LSBtZXJnZSh4ID0gc2V1cmF0Lmxpc3QkUk5BLCB5ID0gc2V1cmF0Lmxpc3QkQVRBQykKCmNvZW1iZWQgPC0gU2NhbGVEYXRhKGNvZW1iZWQsIGZlYXR1cmVzID0gaW50ZWdyYXRlX2ZlYXR1cmVzX3VuaW9uLCBkby5zY2FsZSA9IEZBTFNFKQpjb2VtYmVkIDwtIFJ1blBDQShjb2VtYmVkLCBmZWF0dXJlcyA9IGludGVncmF0ZV9mZWF0dXJlc191bmlvbiwgdmVyYm9zZSA9IEZBTFNFKQpjb2VtYmVkIDwtIFJ1blVNQVAoY29lbWJlZCwgZGltcyA9IDE6MzApCgpjb2VtYmVkIDwtIEFkZE1ldGFEYXRhKGNvZW1iZWQsIG1ldGFkYXRhID0gaWZlbHNlKGNvbG5hbWVzKGNvZW1iZWQpICVpbiUgY29sbmFtZXMoc2V1cmF0Lmxpc3RbW3JlZmVyZW5jZV1dKSwgcmVmZXJlbmNlLCBxdWVyeSksIGNvbC5uYW1lID0gInRlY2giKQpgYGAKCgpgYGB7ciwgZWNobz1GQUxTRX0KIyMgTG9hZCBvdXRwdXQgZm9yIHF1aWNrIGtuaXR0aW5nCmNvZW1iZWQgPC0gcmVhZFJEUygifi9teV9kYXRhL1RjZWxsc19DQ0FfaW50ZWdyYXRpb25fc2V1cmF0XzIwMTkxMjAzLlJtZCIpCmBgYAoKPCEtLSBSdW4gQ29ub3MgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIGRhdGEucHJvY2Vzc2VkIDwtIG1hcChzY2UubGlzdCwgfiBhcy5TZXVyYXQoLngpKSAgLS0+CjwhLS0gVmFyaWFibGVGZWF0dXJlcyhkYXRhLnByb2Nlc3NlZFtbcmVmZXJlbmNlXV0pIDwtIGludGVncmF0ZV9mZWF0dXJlc191bmlvbiAtLT4KPCEtLSBWYXJpYWJsZUZlYXR1cmVzKGRhdGEucHJvY2Vzc2VkW1txdWVyeV1dKSA8LSBpbnRlZ3JhdGVfZmVhdHVyZXNfdW5pb24gLS0+CjwhLS0gZGF0YS5wcm9jZXNzZWQgPC0gbWFwKGRhdGEucHJvY2Vzc2VkLCB+IFNjYWxlRGF0YSgueCkgJT4lIFJ1blBDQShkaW1zPTE6MzApKSAtLT4KPCEtLSBsLmNvbiA8LSBDb25vcyRuZXcoZGF0YS5wcm9jZXNzZWQsbi5jb3Jlcz0zMCkgLS0+CjwhLS0gbC5jb24kYnVpbGRHcmFwaChrPTE1LGsuc2VsZj01LGsuc2VsZi53ZWlnaD0wLjAxLG5jb21wcz0zMCxuLm9kZ2VuZXM9NWUzLHNwYWNlPSdQQ0EnKSAgLS0+Cgo8IS0tIGwuY29uJGZpbmRDb21tdW5pdGllcyhyZXNvbHV0aW9uPTEuNSkgLS0+CjwhLS0gbC5jb24kZW1iZWRHcmFwaChhbHBoYT0xLzIpIC0tPgoKPCEtLSBjb25vcy5vdXQgPC0gY29ub3MubW9kZWwkbW9kZWwgLS0+CjwhLS0gbC5jb24kcGxvdEdyYXBoKGNvbG9yLmJ5ID0gInNhbXBsZSIpIC0tPgoKPCEtLSBnZW5lWCA8LSBzZXVyYXQubGlzdFtbcmVmZXJlbmNlXV1AYXNzYXlzJFJOQUBzY2FsZS5kYXRhWzMsXSAtLT4KPCEtLSBnZW5lWCA8LSBzZXROYW1lcyhhbm5vdGF0aW9uWywxXSwgcm93bmFtZXMoYW5ub3RhdGlvbikpIC0tPgo8IS0tIG5ldy5sYWJlbC5wcm9iYWJpbGl0aWVzIDwtIGwuY29uJHByb3BhZ2F0ZUxhYmVscyhsYWJlbHMgPSBnZW5lWCwgdmVyYm9zZSA9IFQsIGZpeGVkLmluaXRpYWwubGFiZWxzPVQpIC0tPgo8IS0tIGhpc3QobmV3LmxhYmVsLnByb2JhYmlsaXRpZXMpIC0tPgo8IS0tIGwuY29uJGNvcnJlY3RHZW5lcyhnZW5lcyA9IGludGVncmF0ZV9mZWF0dXJlc191bmlvbiwgY291bnQubWF0cml4ID0gTWF0cml4KHNldXJhdC5saXN0JEFUQUNAYXNzYXlzJEFUQUNAZGF0YSkpIC0tPgoKPCEtLSBgYGAgLS0+CgojIyMjIFRyYW5zZmVyIGxhYmVscyBvbiBBVEFDIGRhdGFzZXQKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD01LCBldmFsPUZBTFNFfQpjZWxsdHlwZS5wcmVkaWN0aW9ucyA8LSBUcmFuc2ZlckRhdGEoYW5jaG9yc2V0ID0gdHJhbnNmZXIuYW5jaG9ycywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWZkYXRhID0gc2V1cmF0Lmxpc3RbW3JlZmVyZW5jZV1dJGFubm90YXRpb24sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0LnJlZHVjdGlvbiA9IHNldXJhdC5saXN0JEFUQUNbWyJMU0kiXV0pCgpjb2VtYmVkIDwtIEFkZE1ldGFEYXRhKGNvZW1iZWQsIG1ldGFkYXRhID0gY2VsbHR5cGUucHJlZGljdGlvbnMpCmNvZW1iZWRAbWV0YS5kYXRhICU8PiUKICByb3duYW1lc190b19jb2x1bW4oKSAlPiUKICBkcGx5cjo6bXV0YXRlKGFubm90YXRpb249aWZlbHNlKGlzLm5hKHByZWRpY3RlZC5pZCkgLCBhbm5vdGF0aW9uLCBOQSkpICU+JQogIGNvbHVtbl90b19yb3duYW1lcygpCgpjb2VtYmVkQG1ldGEuZGF0YSA8LQogIGNvZW1iZWRAbWV0YS5kYXRhICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigpICU+JQogIGRwbHlyOjptdXRhdGUoYW5ub3RhdGlvbj1pZmVsc2UoaXMubmEoYW5ub3RhdGlvbikgJiBwcmVkaWN0aW9uLnNjb3JlLm1heCA+IDAuNSwgcHJlZGljdGVkLmlkLCBhbm5vdGF0aW9uKSkgJT4lCiAgZHBseXI6Om11dGF0ZShhbm5vdGF0aW9uPWlmZWxzZShhbm5vdGF0aW9uPT0iU1AgKDIpIiwgTkEsIGFubm90YXRpb24pKSAlPiUKICBjb2x1bW5fdG9fcm93bmFtZXMoKQpgYGAKCmBgYHtyfQpDb21iaW5lUGxvdHMoCiAgbGlzdChEaW1QbG90KGNvZW1iZWQsIGdyb3VwLmJ5ID0gYygicHJlZGljdGVkLmlkIiksIGNvbHMgPSBjZWxsLnR5cGUucGFsKSArIGdndGl0bGUoInByZWRpY3Rpb24iKSwKICBEaW1QbG90KGNvZW1iZWQsIGdyb3VwLmJ5ID0gYygiYW5ub3RhdGlvbiIpLCBjb2xzID0gY2VsbC50eXBlLnBhbCkgKyBnZ3RpdGxlKCJPcmlnaW5hbCArIHByZWRpY3Rpb24iKSksCiAgbGVnZW5kID0gInRvcCIKICApCmBgYApgYGB7cn0KRmVhdHVyZVBsb3QoY29lbWJlZCwgZmVhdHVyZXMgPSAicHJlZGljdGlvbi5zY29yZS5tYXgiLCBjZWxscyA9IHdoaWNoKGNvZW1iZWQkdGVjaD09IkFUQUMiKSkgKyBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKQpgYGAKCgojIyMgUnVuIFBzZXVkb3RpbWUgYW5hbHlzaXMgCgpJZGVudGlmeSBjZWxsIG9mIG9yaWdpbiBhbW9uZyB0aGUgRE4gY2VsbHMgYmFzZWQgb24gZXhwcmVzc2lvbiBvZiBJR0xMMSBhbmQgQ0QzNAoKYGBge3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMH0KRmVhdHVyZVBsb3QoY29lbWJlZCwgZmVhdHVyZXMgPSBjKCJJR0xMMSIsICJDRDM0IiksIHNwbGl0LmJ5ID0gInRlY2giLCBzbG90ID0gImRhdGEiLCBjb2xzID0gdmlyaWRpczo6dmlyaWRpcyhuPTEwMCkpCmBgYApgYGB7ciwgZXZhbD1GQUxTRX0KY2VsbC5vbyA8LQogIGNvZW1iZWRAbWV0YS5kYXRhICU+JSAKICByb3duYW1lc190b19jb2x1bW4oImNlbGwiKSAlPiUKICBtdXRhdGUoSUdMTDE9Y29lbWJlZEBhc3NheXMkUk5BQGNvdW50c1siSUdMTDEiLGNlbGxdKSAlPiUKICBzZWxlY3QoY2VsbCwgYW5ub3RhdGlvbiwgSUdMTDEpICU+JQogIGFycmFuZ2UoLUlHTEwxKSAlPiUKICBmaWx0ZXIoYW5ub3RhdGlvbj09IkROIikgJT4lCiAgdG9wX24oMSwgSUdMTDEpICU+JQogIHB1bGwoY2VsbCkKCmNvZW1iZWRAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5ncyAlPiUKICBhcy50aWJibGUocm93bmFtZXM9ImNlbGwiKSAlPiUKICBtdXRhdGUoY2VsbC5vbyA9IGlmZWxzZShjZWxsICVpbiUgY2VsbC5vbywgVCwgRikpICU+JQogIGdncGxvdChhZXMoVU1BUF8xLCBVTUFQXzIpKSArCiAgZ2VvbV9wb2ludChjb2xvcj0iZ3JleTUwIikgKwogIGdlb21fcG9pbnQoZGF0YT0uICU+JSBmaWx0ZXIoY2VsbC5vbyksY29sb3I9J3JlZCcpICsKICBnZ3JlcGVsOjpnZW9tX3RleHRfcmVwZWwoZGF0YT0uICU+JSBmaWx0ZXIoY2VsbC5vbyksIGFlcyhsYWJlbD0iY2VsbCBvZiBvcmlnaW4iKSwgY29sb3I9J3JlZCcpICsKICB0aGVtZV9jb3dwbG90KCkgCgpjb2VtYmVkIDwtIEFkZE1ldGFEYXRhKGNvZW1iZWQsIGlmZWxzZShjb2xuYW1lcyhjb2VtYmVkKT09Y2VsbC5vbywgVFJVRSwgRkFMU0UpLCBjb2wubmFtZSA9ICJpcm9vdF9jZWxsIikKCiAgCmBgYAoKCmBgYHtyLCBldmFsPUZBTFNFfQptZXJnZWQuc2NlIDwtIFNpbmdsZUNlbGxFeHBlcmltZW50KGxpc3QoY291bnRzPWNvZW1iZWRAYXNzYXlzJFJOQUBjb3VudHMsIGxvZ2NvdW50cz1jb2VtYmVkQGFzc2F5cyRSTkFAZGF0YSksIGNvbERhdGE9Y29lbWJlZEBtZXRhLmRhdGFbLCBjKCJhbm5vdGF0aW9uIiwgInRlY2giLCAiaXJvb3RfY2VsbCIpXSwKICAgICAgICAgICAgICAgICAgICAgcmVkdWNlZERpbXMgPSBtYXAoY29lbWJlZEByZWR1Y3Rpb25zLCB+IC54QGNlbGwuZW1iZWRkaW5ncykpCgpzYXZlUkRTKG9iamVjdCA9IG1lcmdlZC5zY2UsICJ+L215X2RhdGEvVGNlbGxzX0NDQV9pbnRlZ3JhdGlvbl8yMDE5MTIwMy5SRFMiKQpzYXZlUkRTKG9iamVjdCA9IGludGVncmF0ZV9mZWF0dXJlc191bmlvbiwgIn4vbXlfZGF0YS9pbnRGZWF0dXJlc19UY2VsbHNfQ0NBX2ludGVncmF0aW9uXzIwMTkxMjAzLlJEUyIpCmBgYAoKSSBpbmZlciBwc2V1ZG90aW1lIHVzaW5nIHRoZSBkaWZmdXNpb24gcHNldWRvdGltZSBhbGdvcml0aG0gYXMgaW1wbGVtZW50ZWQgaW4gc2NhbnB5LiBNYWtpbmcgYW4gUi9yZXRpY3VsYXRlIHdyYXBwZXIgZm9yIHRoaXMgZnVuY3Rpb24gd291bGQgYmUgbmljZSwgYnV0IGZvciBub3csIHNlZSBgbXVsdGlPbWljX2JlbmNobWFyay9EUFRfdGNlbGxzLmlweW5iYC4KClJlYWQgc2NhbnB5IG91dHB1dCBhbmQgc2F2ZSBpbiBSIG9iamVjdC4KYGBge3IsIGV2YWw9RkFMU0V9CmRwdCA8LSByZWFkLmNzdignfi9teV9kYXRhL1RjZWxsc19DQ0FfaW50ZWdyYXRpb25fMjAxOTExMjdfc2NhbnB5X2RwdC5jc3YnKSAlPiUKICBzZWxlY3QoWCwgZHB0X3BzZXVkb3RpbWUpCgpjb2VtYmVkIDwtIEFkZE1ldGFEYXRhKGNvZW1iZWQsIGNvbHVtbl90b19yb3duYW1lcyhkcHQsICdYJykpCnNhdmVSRFMoY29lbWJlZCwgIn4vbXlfZGF0YS9UY2VsbHNfQ0NBX2ludGVncmF0aW9uX3NldXJhdF8yMDE5MTIwMy5SbWQiKQpjb2VtYmVkIDwtIHJlYWRSRFMoIn4vbXlfZGF0YS9UY2VsbHNfQ0NBX2ludGVncmF0aW9uX3NldXJhdF8yMDE5MTIwMy5SbWQiKQpgYGAKCgpWaXN1YWxpemUgcHNldWRvdGltZQoKYGBge3IsIGZpZy53aWR0aD0xMH0KRmVhdHVyZVBsb3QoY29lbWJlZCwgcmVkdWN0aW9uID0gInVtYXAiLCBmZWF0dXJlID0gImRwdF9wc2V1ZG90aW1lIiwgc3BsaXQuYnkgPSAidGVjaCIsIGNvbD12aXJpZGlzOjp2aXJpZGlzKDEwKSkgCmBgYAoKU2F2ZSBmaWd1cmUKYGBge3IsIGZpZy53aWR0aD0xMH0KY29lbWJlZC51bWFwcy5wbCA8LSBwbG90X2dyaWQoCiAgRGltUGxvdChjb2VtYmVkLCBncm91cC5ieSA9IGMoInRlY2giKSkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIiksCiAgRGltUGxvdChjb2VtYmVkLCBncm91cC5ieSA9IGMoImFubm90YXRpb24iKSwgY29scyA9IGNlbGwudHlwZS5wYWwsIGxhYmVsID0gVFJVRSwgbGFiZWwuc2l6ZSA9IDUpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSwKICBGZWF0dXJlUGxvdChjb2VtYmVkLCByZWR1Y3Rpb24gPSAidW1hcCIsIGZlYXR1cmUgPSAiZHB0X3BzZXVkb3RpbWUiKSArIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYyhuYW1lPSJEaWZmdXNpb25cbnBzZXVkb3RpbWUiKSArIGdndGl0bGUoIiIpLAogIG5yb3c9MSwgbmNvbD0zLCByZWxfd2lkdGhzID0gYygxLDEsMS4yKSwKICBsYWJlbHMgPSBjKCJBIiwgIkIiLCAiQyIpCikgCgpjb2VtYmVkLnVtYXBzLnBsICsKICBnZ3NhdmUocGFzdGUwKG91dGRpciwgImNvZW1iZWRfdW1hcHMucG5nIiksIHdpZHRoPTEyLCBoZWlnaHQgPSA0KQpgYGAKCgoKYGBge3IsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTEwfQpjb2VtYmVkQG1ldGEuZGF0YSAlPiUKICBkcGx5cjo6bXV0YXRlKGBEUFQgcmFua2A9ZGVuc2VfcmFuayhkcHRfcHNldWRvdGltZSkpICU+JQogIGdncGxvdChhZXMoYERQVCByYW5rYCkpICsKICBnZW9tX2hpc3RvZ3JhbShhZXMoZmlsbD1hbm5vdGF0aW9uKSwgYmlucz01MCkgKwogIGZhY2V0X2dyaWQoYW5ub3RhdGlvbn50ZWNoLCBzY2FsZXM9ImZyZWVfeSIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNlbGwudHlwZS5wYWwpCgpgYGAKCkNoZWNrIGV4cHJlc3Npb24gb2YgbWFya2VycyBhbG9uZyBwc2V1ZG90aW1lCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9NH0KY29lbWJlZEBhc3NheXMkUk5BQGRhdGFbdC5jZWxsLm1hcmtlcnMka25vd24ubWFya2VycywgXSAlPiUKICBhcy5tYXRyaXgoKSAlPiUKICByZXNoYXBlMjo6bWVsdCh2YXJuYW1lcz1jKCJnZW5lIiwgImNlbGwiKSkgJT4lCiAgbGVmdF9qb2luKGNvZW1iZWRAbWV0YS5kYXRhWywiZHB0X3BzZXVkb3RpbWUiLCBkcm9wPUZdICU+JSByb3duYW1lc190b19jb2x1bW4oImNlbGwiKSkgJT4lCiAgbXV0YXRlKHBzZXVkb3RpbWUucmFuaz1kZW5zZV9yYW5rKGRwdF9wc2V1ZG90aW1lKSkgJT4lCiAgZ3JvdXBfYnkoZ2VuZSkgJT4lCiAgYXJyYW5nZShwc2V1ZG90aW1lLnJhbmspICU+JQogICMgbXV0YXRlKHZhbHVlPSh2YWx1ZS1taW4odmFsdWUpKS9tYXgodmFsdWUpLW1pbih2YWx1ZSkpICU+JQogIG11dGF0ZSh2YWx1ZT16b286OnJvbGxtZWFuKHZhbHVlLCBrPTUsIGZpbGw9TkEpKSAlPiUgCiAgIyBtdXRhdGUodmFsdWU9KHZhbHVlLW1lYW4odmFsdWUpKS9zZCh2YWx1ZSkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBtdXRhdGUoZ2VuZT1mYWN0b3IoZ2VuZSwgbGV2ZWxzPXJldih1bmlxdWUoZ2VuZSkpKSkgJT4lCiAgZ2dwbG90KGFlcyhwc2V1ZG90aW1lLnJhbmssIGdlbmUsIGZpbGw9dmFsdWUpKSArCiAgZ2VvbV90aWxlKCkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG5hbWU9ImxvZyBleHByZXNzaW9uIikgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCkJpbiBwc2V1ZG90aW1lIGFuZCB2aXN1YWxpemUgY2VsbCB0eXBlIGNvbXBvc2l0aW9uCgpgYGB7ciwgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTR9CmRwdC5kZiA8LSAKICBjb2VtYmVkQG1ldGEuZGF0YSAlPiUKICByb3duYW1lc190b19jb2x1bW4oImNlbGwiKSAlPiUKICBkcGx5cjo6bXV0YXRlKGRwdF9yYW5rPWRlbnNlX3JhbmsoZHB0X3BzZXVkb3RpbWUpKSAlPiUKICBtdXRhdGUoZHB0X2Jpbj1jdXQoZHB0X3JhbmssIGJyZWFrcyA9IDEwMCkpICU+JQogIG11dGF0ZShkcHRfYmluPWFzLm51bWVyaWMoZHB0X2JpbikpICU+JQogIHNlbGVjdChjZWxsLHRlY2gsIGFubm90YXRpb24sIHByZWRpY3Rpb24uc2NvcmUubWF4LCBkcHRfYmluLCBkcHRfcHNldWRvdGltZSwgZHB0X3JhbmspCgpjZWxsLnR5cGUucGwgPC0gZHB0LmRmICU+JQogIGdncGxvdChhZXMoZHB0X2JpbiwgZmlsbCA9IGFubm90YXRpb24pKSArCiAgIyBnZW9tX2hpc3RvZ3JhbShiaW5zPTEwMCkgKwogIGdlb21fYmFyKCkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jZWxsLnR5cGUucGFsLCBuYS52YWx1ZT0iZ3JleTUwIikgKwogIGZhY2V0X2dyaWQodGVjaH4uLCBzY2FsZXM9ImZyZWVfeSIpICsKICB4bGFiKCJQc2V1ZG90aW1lIGJpbiIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikKCmNlbGwudHlwZS5wbApgYGAKCkNvcnJlbGF0aW9uIGJldHdlZW4gZ2xvYmFsIGFjY2Vzc2liaWxpdHkgYW5kIHBzZXVkb3RpbWUgb3JkZXJpbmcuCgpgYGB7ciwgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTh9CnNuYXAub3V0IDwtIHJlYWRSRFMoZmlsZSA9ICJ+L215X2RhdGEvY2VsbHJhbmdlci1hdGFjMTEwX2NvdW50XzMwNDM5X1dTU1M4MDM4MzYwX0dSQ2gzOC0xXzFfMC5zbmFwQVRBQy5SRFMiKQoKZ3JvdXBzIDwtIGRwdC5kZltkcHQuZGYkdGVjaD09IkFUQUMiLCBjKCJjZWxsIiwgImRwdF9iaW4iKV0KYm1hdCA8LSBzbmFwLm91dEBibWF0W3N0cl9yZW1vdmUoZ3JvdXBzJGNlbGwsICJBVEFDXyIpLF0KZnJhYy5hY2Nlc3NpYmxlIDwtIHJvd1N1bXMoYm1hdCkvbmNvbChibWF0KQphY2MuZnJhY3Rpb24ucGwgPC0gZ3JvdXBzICU+JQogIG11dGF0ZShmcmFjX2FjY2Vzc2libGU9ZnJhYy5hY2Nlc3NpYmxlW3N0cl9yZW1vdmUoY2VsbCwgIkFUQUNfIildKSAlPiUKICBnZ3Bsb3QoYWVzKGRwdF9iaW4sIGZyYWNfYWNjZXNzaWJsZSkpICsKICBnZW9tX2JveHBsb3QoYWVzKGdyb3VwPWFzLmZhY3RvcihkcHRfYmluKSksIG91dGxpZXIuYWxwaGEgPSAwLjMsIG91dGxpZXIuc2l6ZSA9IDAuNykgKwogICMgZ2VvbV9qaXR0ZXIoYWxwaGE9MC4xKSArCiAgeGxhYigiUHNldWRvdGltZSBiaW4iKSArCiAgeWxhYigiRnJhY3Rpb24gb2YgYWNjZXNzaWJsZSBiaW5zIikgKwogIGZhY2V0X2dyaWQoJ0FUQUMnfi4pICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgCiAgCmRwdC5wbCA8LSBwbG90X2dyaWQoY2VsbC50eXBlLnBsICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJ0b3AiLCBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpLCAKICAgICAgICAgIGFjYy5mcmFjdGlvbi5wbCwgCiAgICAgICAgICBhbGlnbiA9ICJ2IiwgbmNvbD0xLCBucm93PTIsIGF4aXM9ImwiKQoKZHB0LnBsICsKICBnZ3NhdmUocGFzdGUwKG91dGRpciwgIkRQVF9iaW5zLnBuZyIpLCB3aWR0aD0xMCwgaGVpZ2h0ID0gNykKYGBgCgoKPCEtLSBWaXogbWFya2VycyAtLT4KPCEtLSBgYGB7cn0gLS0+CjwhLS0gYWNjLm1hdCA8LSBjb2VtYmVkQGFzc2F5cyRBVEFDQGRhdGEgLS0+CjwhLS0gbWFya2Vycy5hY2MgPC0gYWNjLm1hdFtpbnRlcnNlY3QoYyh0LmNlbGwubWFya2VycyRrbm93bi5tYXJrZXJzLCB0LmNlbGwubWFya2VycyRjaGVtb2tpbmUucmVjZXB0b3JzLCB0LmNlbGwubWFya2VycyRyZWNvbWJpbmF0aW9uKSwgcm93bmFtZXMoYWNjLm1hdCkpLCwgZHJvcD1GXSAtLT4KCjwhLS0gbWFya2Vycy5kZiA8LSBkYXRhLmZyYW1lKHQoYXMubWF0cml4KG1hcmtlcnMuYWNjWyxkcHQuZGYkY2VsbFtkcHQuZGYkdGVjaD09IkFUQUMiXV0pKSkgJT4lIC0tPgo8IS0tICAgcm93bmFtZXNfdG9fY29sdW1uKCJjZWxsIikgJT4lIC0tPgo8IS0tICAgcGl2b3RfbG9uZ2VyKGNvbHMgPSByb3duYW1lcyhtYXJrZXJzLmFjYyksIG5hbWVzX3RvID0gIm1hcmtlci5nZW5lIiwgdmFsdWVzX3RvID0gImFjY2Vzc2liaWxpdHkiKSAtLT4KCjwhLS0gYW5ub3RhdGlvbi5obSA8LSBhdGFjLmRwdC5kZiAlPiUgLS0+CjwhLS0gICBncm91cF9ieShkcHRfYmluLCBhbm5vdGF0aW9uKSAlPiUgLS0+CjwhLS0gICBzdW1tYXJpc2Uobj1uKCkpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoZHB0X2JpbiwgYW5ub3RhdGlvbikpICsgLS0+CjwhLS0gICBnZW9tX3RpbGUoYWVzKGFscGhhPW4sIGZpbGw9YW5ub3RhdGlvbikpICArIC0tPgo8IS0tICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNikgKyAtLT4KPCEtLSAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jZWxsLnR5cGUucGFsLCBuYS52YWx1ZT0iZ3JleTUwIikgKyAtLT4KPCEtLSAgIGd1aWRlcyhmaWxsPSdub25lJywgYWxwaGE9J25vbmUnKSArIC0tPgo8IS0tICAgdGhlbWUoYXhpcy5saW5lID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKSAtLT4KCjwhLS0gbWFya2Vycy5obSA8LSBhdGFjLmRwdC5kZiAlPiUgLS0+CjwhLS0gICBmdWxsX2pvaW4obWFya2Vycy5kZikgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkoZHB0X2JpbiwgbWFya2VyLmdlbmUpICU+JSAtLT4KPCEtLSAgIHN1bW1hcmlzZShmcmFjX2FjY2Vzc2libGU9c3VtKGFjY2Vzc2liaWxpdHkpL24oKSkgJT4lIC0tPgo8IS0tICAgdW5ncm91cCgpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShtYXJrZXIuZ2VuZT1mYWN0b3IobWFya2VyLmdlbmUsIGxldmVscyA9IGMoIkNEMzQiLCAiSUdMTDEiLCAiVFJHQzIiLCAiVFJEQyIsICJQVENSQSIsICJUUkJDMiIsICJDQ1I5IiwiQ0NSNyIsICJSQUcxIiwgIlJBRzIiLCAiVFJBQyIsICJDRDQiLCAiQ0Q4QSIsICJDRDhCIikpKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUobWFya2VyLmdlbmU9ZmFjdG9yKG1hcmtlci5nZW5lLCBsZXZlbHMgPSByZXYobGV2ZWxzKG1hcmtlci5nZW5lKSkpKSAlPiUgLS0+CjwhLS0gICBncm91cF9ieShtYXJrZXIuZ2VuZSkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKGZyYWNfYWNjZXNzaWJsZT0oZnJhY19hY2Nlc3NpYmxlIC0gbWluKGZyYWNfYWNjZXNzaWJsZSkpL21heChmcmFjX2FjY2Vzc2libGUpIC0gbWluKGZyYWNfYWNjZXNzaWJsZSkpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoZHB0X2JpbiwgbWFya2VyLmdlbmUsIGZpbGw9ZnJhY19hY2Nlc3NpYmxlKSkgKyAgLS0+CjwhLS0gICBnZW9tX3RpbGUoKSArIC0tPgo8IS0tICAgc2NhbGVfZmlsbF92aXJpZGlzX2MobmFtZT0iRnJhYy5jZWxscyIpICsgLS0+CjwhLS0gICB4bGFiKCJQc2V1ZG90aW1lIGJpbiIpICsgLS0+CjwhLS0gICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE2KSArIC0tPgo8IS0tICAgdGhlbWUoYXhpcy5saW5lID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkgLS0+Cgo8IS0tIGxlZyA8LSBnZXRfbGVnZW5kKG1hcmtlcnMuaG0pIC0tPgo8IS0tIGdyMSA8LSBwbG90X2dyaWQoYW5ub3RhdGlvbi5obSwgbWFya2Vycy5obSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiksIG5yb3c9MiwgcmVsX2hlaWdodHMgPSBjKDEsMiksIGFsaWduID0gInYiKSAtLT4KPCEtLSBncjIgPC0gcGxvdF9ncmlkKGdncGxvdCgpICsgIHRoZW1lX3ZvaWQoKSxsZWcsIG5yb3c9MiwgcmVsX2hlaWdodHMgPSBjKDEsMikpIC0tPgo8IS0tIHBsb3RfZ3JpZChncjEsIGdyMiwgcmVsX3dpZHRocyA9IGMoMywxKSkgLS0+CjwhLS0gYGBgIC0tPgoKIyMgTW90aWYgYW5hbHlzaXMgCgpJIGluaXRpYWxseSB3YW50ZWQgdG8gY2FsbCBwZWFrcyBmcm9tIFNuYXBBVEFDIGNsdXN0ZXJzLCB0aGVuIGJ1aWxkIGEgY2VsbCB4IHBlYWsgbWF0cml4IG9uIHRob3NlIGRldGVjdGVkIHBlYWtzLCBidXQgU25hcEFUQUMvTUFDUzIgZG9uJ3Qgc2VlbSB0byB3b3JrLiAKCjwhLS0gLS0tLSAtLT4KPCEtLSAqKlRoaXMgZG9lc24ndCBzZWVtIHRvIHdvcmsqKiAtLT4KPCEtLSBDYWxsIHBlYWtzICAtLT4KPCEtLSBgYGB7cn0gLS0+CjwhLS0gIyMgQ2FsbCBwZWFrcyBvbiBjbHVzdGVycyAtLT4KPCEtLSBjbHVzdGVycy5zZWwgPC0gdW5pcXVlKHRjZWxscy5zY2UuYXRhYyRzZXVyYXRfY2x1c3RlcnMpIC0tPgo8IS0tIHBlYWtzLmxzID0gbWNsYXBwbHkoc2VxKGNsdXN0ZXJzLnNlbCksIGZ1bmN0aW9uKGkpeyAtLT4KPCEtLSAgIHByaW50KHBhc3RlKCJjbHVzdGVyIiwgY2x1c3RlcnMuc2VsW2ldKSkgLS0+CjwhLS0gICBwZWFrcyA9IHJ1bk1BQ1MoIC0tPgo8IS0tICAgICAgIG9iaj1zbmFwLm91dFt3aGljaChzbmFwLm91dEBtZXRhRGF0YSRiYXJjb2RlICVpbiUgY29sbmFtZXModGNlbGxzLnNjZS5hdGFjKVt0Y2VsbHMuc2NlLmF0YWMkc2V1cmF0X2NsdXN0ZXJzPT1jbHVzdGVycy5zZWxbaV1dKSxdLCAgLS0+CjwhLS0gICAgICAgb3V0cHV0LnByZWZpeD1wYXN0ZTAoIlRjZWxsc19GNzRfY2x1c3RlciIsIGNsdXN0ZXJzLnNlbFtpXSksIC0tPgo8IS0tICAgICAgIHBhdGgudG8uc25hcHRvb2xzPSIvb3B0L2NvbmRhL2Jpbi9zbmFwdG9vbHMiLCAtLT4KPCEtLSAgICAgICBwYXRoLnRvLm1hY3M9Ii9vcHQvY29uZGEvYmluL21hY3MyIiwgLS0+CjwhLS0gICAgICAgZ3NpemU9ImhzIiwgIyBtbSwgaHMsIGV0YyAtLT4KPCEtLSAgICAgICBidWZmZXIuc2l6ZT01MDAsICAtLT4KPCEtLSAgICAgICBudW0uY29yZXM9MywgLS0+CjwhLS0gICAgICAgbWFjcy5vcHRpb25zPSItLW5vbW9kZWwgLS1zaGlmdCAxMDAgLS1leHQgMjAwIC0tcXZhbCA1ZS0yIC1CIC0tU1BNUiIsIC0tPgo8IS0tICAgICAgIHRtcC5mb2xkZXI9dGVtcGRpcigpIC0tPgo8IS0tICApIC0tPgo8IS0tIHBlYWtzIC0tPgo8IS0tIH0sIG1jLmNvcmVzPTUpIC0tPgoKPCEtLSBwZWFrcy5uYW1lcyA9IGxpc3QuZmlsZXMoIn4vbXlfZGF0YS9UY2VsbHNfcGVha3MvIiwgcGF0dGVybj0ibmFycm93UGVhayIsIGZ1bGwubmFtZXMgPSBUKSAtLT4KPCEtLSBwZWFrLmdyLmxzID0gbGFwcGx5KHBlYWtzLm5hbWVzLCBmdW5jdGlvbih4KXsgLS0+CjwhLS0gICBwZWFrLmRmID0gcmVhZC50YWJsZSh4KSAtLT4KPCEtLSAgIEdSYW5nZXMoc3RyX3JlbW92ZV9hbGwocGVhay5kZlssMV0sICJiJ3wnIiksIElSYW5nZXMocGVhay5kZlssMl0sIHBlYWsuZGZbLDNdKSkgLS0+CjwhLS0gfSkgLS0+CjwhLS0gcGVhay5nciA9IHJlZHVjZShSZWR1Y2UoYywgcGVhay5nci5scykpIC0tPgoKPCEtLSAjIyBNYWtlIGNlbGwgYnkgcGVhayBtYXRyaXggKG5vdCBydW4gaGVyZSkgLS0+CjwhLS0gcGVha3MuZGYgPSBhcy5kYXRhLmZyYW1lKHBlYWsuZ3IpWywxOjNdOyAtLT4KPCEtLSB3cml0ZS50YWJsZShwZWFrcy5kZixmaWxlID0gIn4vbXlfZGF0YS9UY2VsbHNfcGVha3MvcGVha3MuY29tYmluZWQuYmVkIixhcHBlbmQ9RkFMU0UsIC0tPgo8IS0tIAkJcXVvdGU9IEZBTFNFLHNlcD0iXHQiLCBlb2wgPSAiXG4iLCBuYSA9ICJOQSIsIGRlYyA9ICIuIiwgIC0tPgo8IS0tIAkJcm93Lm5hbWVzID0gRkFMU0UsIGNvbC5uYW1lcyA9IEZBTFNFLCBxbWV0aG9kID0gYygiZXNjYXBlIiwgImRvdWJsZSIpLCAtLT4KPCEtLSAJCWZpbGVFbmNvZGluZyA9ICIiKSAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tIE1ha2luZyBjb21tb24gcGVhayByZWZlcmVuY2Ugd2l0aCBzbmFwdG9vbHMuIEluIHRlcm1pbmFsIC0tPgo8IS0tIGBgYCAtLT4KPCEtLSBzbmFwdG9vbHMgc25hcC1hZGQtcG1hdCAtLXNuYXAtZmlsZSB+L215X2RhdGEvY2VsbHJhbmdlci1hdGFjMTEwX2NvdW50XzMwNDM5X1dTU1M4MDM4MzYwX0dSQ2gzOC0xXzFfMC5zbmFwIC0tcGVhay1maWxlIHBlYWtzLmNvbWJpbmVkLmJlZCAgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSBBZGQgcG1hdCB0byBzbmFwIG9iamVjdCAtLT4KPCEtLSBgYGB7cn0gLS0+CjwhLS0gc25hcC5vdXQgPC0gY3JlYXRlUG1hdChzbmFwLm91dCwgcGVhay5nciwgZG8ucGFyID0gVCwgbnVtLmNvcmVzID0gMTApIC0tPgo8IS0tIGBgYCAtLT4KPCEtLSAtLS0gLS0+CgpBbHRlcm5hdGl2ZTogbG9hZCBwZWFrIG1hdHJpeCBmcm9tIGNlbGxyYW5nZXIgYW5kIGFkZCB0byBzbmFwIG9iamVjdApgYGB7ciwgZXZhbD1GQUxTRX0KZmlsdC5wZWFrcyA8LSBSZWFkMTBYX2g1KCJ+L215X2RhdGEvZmlsdGVyZWRfcGVha19iY19tYXRyaXguaDUiKQpwZWFrcy5tYXQgPC0gc3RyX3NwbGl0KHJvd25hbWVzKGZpbHQucGVha3MpLCBwYXR0ZXJuID0gIjp8LSIpICU+JSBtYXAocmJpbmQpICU+JSBwdXJycjo6cmVkdWNlKHJiaW5kKQpwZWFrcy5nciA8LSBHUmFuZ2VzKHBlYWtzLm1hdFssMV0sIElSYW5nZXMoYXMubnVtZXJpYyhwZWFrcy5tYXRbLDJdKSwgYXMubnVtZXJpYyhwZWFrcy5tYXRbLDNdKSkpCnNuYXAucG1hdCA8LSBjcmVhdGVTbmFwRnJvbVBtYXQobWF0PXQoZmlsdC5wZWFrc1ssc25hcC5vdXRAYmFyY29kZV0pLCBiYXJjb2Rlcz1zbmFwLm91dEBiYXJjb2RlLCBwZWFrcz1wZWFrcy5ncikKc25hcC5wbWF0CmBgYAoKQ2FsY3VsYXRpbmcgZGV2aWF0aW9ucyBpbiBURiBhY2Nlc3NpYmlsaXR5IHVzaW5nIENocm9tVkFSLiBUaGlzIGlzIGEgbWVhc3VyZSBvZiBob3cgbXVjaCBpcyBtb3RpZiBhY2Nlc3NpYmlsaXR5IGluIGVhY2ggY2VsbCBpcyBlbnJpY2hlZCBjb21wYXJlZCB0byBhbGwgdGhlIGNlbGxzIGFuZCBnZW5lcmFsIGNlbGwgY292ZXJhZ2UuIFdoaWxlIFNuYXBBVEFDIGhhcyBhbiB3cmFwcGVyIGFyb3VuZCBDaHJvbVZBUiB0aGF0IG91dHB1dHMgdGhlIGRldmlhdGlvbiBtYXRyaXgsIEkganVzdCB0YWtlIHRoZSBjb2RlIGZyb20gdGhhdCBmdW5jdGlvbiBhbmQgcnVuIGV2ZXJ5IHN0ZXAgc2VwYXJhdGVseSB0byBrZWVwIHRoZSB1c2VmdWwgb3V0cHV0cyBhbmQgc3RhdGlzdGljcyBvZiBjaHJvbVZBUi4KCmBgYHtyLCBldmFsPUZBTFNFfQpzbmFwLnBtYXQgPSBtYWtlQmluYXJ5KHNuYXAucG1hdCwgInBtYXQiKQoKb2JqID0gc25hcC5wbWF0CmlucHV0Lm1hdD0icG1hdCIKbWluLmNvdW50PTEwCnNwZWNpZXM9IkhvbW8gc2FwaWVucyIKZ2Vub21lPUJTZ2Vub21lLkhzYXBpZW5zLlVDU0MuaGczOAoKZGF0YS51c2UgPSBvYmpAcG1hdApwZWFrLnVzZSA9IG9iakBwZWFrCgpuY2VsbCA9IG5yb3coZGF0YS51c2UpCgppZHkgPSB3aGljaChNYXRyaXg6OmNvbFN1bXMoZGF0YS51c2UpID49IG1pbi5jb3VudCkKZGF0YS51c2UgPSBkYXRhLnVzZVssaWR5LGRyb3BwaW5nPVRSVUVdCgkKcGVhay51c2UgPSBwZWFrLnVzZVtpZHldCgpyc2UgPC0gU3VtbWFyaXplZEV4cGVyaW1lbnQoCgkJYXNzYXlzID0gbGlzdChjb3VudHMgPSB0KGRhdGEudXNlKSksIAoJCQkJIHJvd1JhbmdlcyA9IHBlYWsudXNlLCAKCQkJCSBjb2xEYXRhID0gRGF0YUZyYW1lKENlbGxfVHlwZT0xOm5yb3coZGF0YS51c2UpLCBkZXB0aD1NYXRyaXg6OnJvd1N1bXMoZGF0YS51c2UpKQoJKTsKcnNlIDwtIGFkZEdDQmlhcyhyc2UsIGdlbm9tZSA9IGdlbm9tZSk7Cm1vdGlmcyA8LSBnZXRKYXNwYXJNb3RpZnMoY29sbGVjdGlvbiA9ICJDT1JFIiwgc3BlY2llcz1zcGVjaWVzKTsKbW90aWZfbW0gPC0gbWF0Y2hNb3RpZnMobW90aWZzLCByc2UsIGdlbm9tZSA9IGdlbm9tZSk7CmRldiA8LSBjb21wdXRlRGV2aWF0aW9ucyhvYmplY3QgPSByc2UsIGFubm90YXRpb25zID0gbW90aWZfbW0pOwp2YXIgPC0gY29tcHV0ZVZhcmlhYmlsaXR5KGRldikKYGBgCgpTYXZlCmBgYHtyLCBldmFsPUZBTFNFfQpyb3dEYXRhKGRldikgJTw+JQogIGFzLnRpYmJsZShyb3duYW1lcz0ibW90aWYiKSAlPiUKICBmdWxsX2pvaW4odmFyKSAlPiUKICBjb2x1bW5fdG9fcm93bmFtZXMoJ21vdGlmJykgJT4lCiAgRGF0YUZyYW1lKCkKCnNhdmVSRFMoZGV2LCAifi9teV9kYXRhL1RjZWxsc19wZWFrcy9UY2VsbHNfY2hyb21WYXJPdXRwdXQuUkRTIikgIApgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQpkZXYgPC0gcmVhZFJEUygifi9teV9kYXRhL1RjZWxsc19wZWFrcy9UY2VsbHNfY2hyb21WYXJPdXRwdXQuUkRTIikgIApgYGAKCgpWaXN1YWxpemUgZGV2aWF0aW9uIHNjb3JlcyBvZiB0aGUgbW9zdCB2YXJpYWJsZSBtb3RpZnMsIG9yZGVyZWQgaW4gcHNldWRvdGltZS4KCmBgYHtyLCBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9MTB9CnNhbXBsZV9kcHRfYmlucy5kZiA8LSBkcHQuZGYgJT4lCiAgbXV0YXRlKGNlbGw9c3RyX3JlbW92ZShjZWxsLCAiXkFUQUNfIikpICU+JQogIGZpbHRlcih0ZWNoPT0iQVRBQyIpICU+JQogIGFycmFuZ2UoZHB0X3BzZXVkb3RpbWUpCgptb3RpZi50b3B2YXIgPC0gdmFyICU+JSByb3duYW1lc190b19jb2x1bW4oIm1vdGlmIikgJT4lIHRvcF9uKDUwLHZhcmlhYmlsaXR5KSAlPiUgcHVsbChtb3RpZikKdGYudG9wdmFyIDwtIG1vdGlmLnRvcHZhciAlPiUgc3RyX3JlbW92ZSgiLitfIikgJT4lIHN0cl9yZW1vdmUoIlxcKC4rfDouKyIpCm1tYXQudG9wdmFyIDwtIGRldkBhc3NheXMkZGF0YSR6W21vdGlmLnRvcHZhcixzYW1wbGVfZHB0X2JpbnMuZGYkY2VsbF0KCnJvd25hbWVzKG1tYXQudG9wdmFyKSA8LSB0Zi50b3B2YXIKc21vb3RoLm1tYXQgPC0gYXBwbHkobW1hdC50b3B2YXIsIDEsIGZ1bmN0aW9uKHgpIHpvbzo6cm9sbG1lYW4oeCwgaz0zMCkpICU+JSB0KCkgCgpwbmcocGFzdGUwKG91dGRpciwgImNocm9tVkFSX21vdGlmX2hlYXRtYXAucG5nIiksIHdpZHRoPTEwMDAsIGhlaWdodCA9IDkwMCkKc21vb3RoLm1tYXQgJT4lCiAgIyBtbWF0LnRvcHZhclssc2FtcGxlX2RwdF9iaW5zLmRmJGNlbGxdICU+JQogIHBoZWF0bWFwOjpwaGVhdG1hcChzaG93X2NvbG5hbWVzID0gRiwgY2x1c3Rlcl9jb2xzID0gRiwgY2x1c3RlcmluZ19kaXN0YW5jZV9yb3dzID0gImNvcnJlbGF0aW9uIiwKICAgICAgICAgICAgICAgICAgICAgYW5ub3RhdGlvbl9jb2wgPSBzYW1wbGVfZHB0X2JpbnMuZGZbLGMoImNlbGwiLCAiYW5ub3RhdGlvbiIsICJkcHRfcHNldWRvdGltZSIpXSAlPiUgY29sdW1uX3RvX3Jvd25hbWVzKCJjZWxsIiksIAogICAgICAgICAgICAgICAgICAgICBhbm5vdGF0aW9uX2NvbG9ycyA9IGxpc3QoYW5ub3RhdGlvbj1jZWxsLnR5cGUucGFsLCBkcHRfcHNldWRvdGltZT12aXJpZGlzOjp2aXJpZGlzKDEwMCkpLCBmb250c2l6ZSA9IDE4LCBmb250c2l6ZV9yb3cgPSAxMiwKICAgICAgICAgICAgICAgICAgICAgIyBjb2xvciA9IGNvbG9yUmFtcFBhbGV0dGUocmV2KGJyZXdlci5wYWwobiA9IDcsIG5hbWUgPSJTcGVjdHJhbCIpKSkoMTAwKSkKICAgICAgICAgICAgICAgICAgICAgYnJlYWtzPXNlcSgtMywzLCBsZW5ndGgub3V0ID0gMTAwKQogICkKZGV2Lm9mZigpCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xMH0KZHB0Lm9yZGVyIDwtCiAgZHB0LmRmICU+JQogIGZpbHRlcih0ZWNoPT0iUk5BIikgJT4lCiAgYXJyYW5nZShkcHRfcHNldWRvdGltZSkgCgpjb2VtYmVkIDwtIFNjYWxlRGF0YShjb2VtYmVkLCBkby5zY2FsZT1UUlVFKQpnZXhtYXQudG9wdmFyIDwtIGNvZW1iZWRAYXNzYXlzJFJOQUBzY2FsZS5kYXRhW3RmLnRvcHZhclt3aGljaCh0Zi50b3B2YXIgJWluJSByb3duYW1lcyhjb2VtYmVkQGFzc2F5cyRSTkFAc2NhbGUuZGF0YSkpXSxkcHQub3JkZXIkY2VsbF0Kc21vb3RoLmdleG1hdCA8LSBhcHBseShnZXhtYXQudG9wdmFyLCAxLCBmdW5jdGlvbih4KSB6b286OnJvbGxtZWFuKHgsIGs9MzApKSAlPiUgdCgpIApzbW9vdGguZ2V4bWF0ICU+JQogICMgdCgpICU+JSBzY2FsZSgpICU+JSB0KCkgJT4lCiAgcGhlYXRtYXA6OnBoZWF0bWFwKHNob3dfY29sbmFtZXMgPSBGLCBjbHVzdGVyX3Jvd3MgPSBULCBjbHVzdGVyX2NvbHMgPSBGLCAKICAgICAgICAgICAgICAgICAgICAgYW5ub3RhdGlvbl9jb2wgPSBkcHQub3JkZXJbLGMoImNlbGwiLCAiYW5ub3RhdGlvbiIsICJkcHRfcHNldWRvdGltZSIpXSAlPiUgY29sdW1uX3RvX3Jvd25hbWVzKCJjZWxsIiksCiAgICAgICAgICAgICAgICAgICAgIGFubm90YXRpb25fY29sb3JzID0gbGlzdChhbm5vdGF0aW9uPWNlbGwudHlwZS5wYWwsIGRwdF9wc2V1ZG90aW1lPXZpcmlkaXM6OnZpcmlkaXMoMTAwKSksIGZvbnRzaXplID0gMTgsIGZvbnRzaXplX3JvdyA9IDEyLAogICAgICAgICAgICAgICAgICAgICBicmVha3M9c2VxKC0yLDIsIGxlbmd0aC5vdXQgPSAxMDApCiAgKQpgYGAKCkNvbXBhcmUgbW90aWYgYWNjZXNzaWJpbGl0eSB0cmVuZCB3aXRoIGdlbmUgZXhwcmVzc2lvbiB0cmVuZCBhbG9uZyBwc2V1ZG90aW1lLiBJIGZpbmQgYm90aCBleGFtcGxlcyBvZiBjb3JyZWxhdGlvbiBiZXR3ZWVuIGFjY2Vzc2liaWxpdHkgYW5kIFRGIGV4cHJlc3Npb24gKGUuZy4gUlVOWDIsIEVMSzMpIGFuZCBhbnRpLWNvcnJlbGF0aW9uIChlLmcuIEpVTiwgRVRWNikuCgpgYGB7cn0KY291bnRzLnRvcHZhciA8LSBjb2VtYmVkQGFzc2F5cyRSTkFAZGF0YVt0Zi50b3B2YXJbd2hpY2godGYudG9wdmFyICVpbiUgcm93bmFtZXMoY29lbWJlZEBhc3NheXMkUk5BQGRhdGEpKV0sZHB0Lm9yZGVyJGNlbGxdCmdleC5kZiA8LSAKICByZXNoYXBlMjo6bWVsdChhcy5tYXRyaXgoY291bnRzLnRvcHZhciksIHZhcm5hbWVzPWMoImdlbmUiLCAiY2VsbCIpKSAlPiUgCiAgIyByb3dpZF90b19jb2x1bW4oImRwdF9vcmRlciIpICU+JQogIG11dGF0ZShkYXRhPSJHZW5lIGV4cHJlc3Npb24iKQptbWF0LmRmIDwtIHJlc2hhcGUyOjptZWx0KG1tYXQudG9wdmFyLCB2YXJuYW1lcz1jKCJnZW5lIiwgImNlbGwiKSkgJT4lCiAgIyByb3dpZF90b19jb2x1bW4oImRwdF9vcmRlciIpICU+JQogIG11dGF0ZShjZWxsPXN0cl9jKCJBVEFDXyIsIGNlbGwpKSAlPiUKICBtdXRhdGUoZGF0YT0iTW90aWYgZGV2aWF0aW9uIikKCnBsb3QudGZzIDwtIGZ1bmN0aW9uKHBsb3QudGZzKXsKICBiaW5kX3Jvd3MoZ2V4LmRmLCBtbWF0LmRmKSAlPiUKICBsZWZ0X2pvaW4oZHB0LmRmWywgYygiY2VsbCIsICJkcHRfcHNldWRvdGltZSIsICJhbm5vdGF0aW9uIildLCBieT0iY2VsbCIpICU+JQogIG11dGF0ZShkcHRfcmFuaz1kZW5zZV9yYW5rKGRwdF9wc2V1ZG90aW1lKSkgJT4lCiAgZHJvcF9uYShkcHRfcHNldWRvdGltZSkgJT4lCiAgICBtdXRhdGUoZGF0YT1mYWN0b3IoZGF0YSwgbGV2ZWxzPWMoIkdlbmUgZXhwcmVzc2lvbiIsICJzbW9vdGgiLCAiTW90aWYgZGV2aWF0aW9uIikpKSAlPiUKICBmaWx0ZXIoZ2VuZSAlaW4lIHBsb3QudGZzKSAlPiUKICBnZ3Bsb3QoYWVzKGRwdF9yYW5rLCB2YWx1ZSwgY29sb3I9ZGF0YSkpICsKICAjIGdlb21fcG9pbnQoZGF0YT0uICU+JSBmaWx0ZXIoZGF0YSE9InNtb290aCIpLCBhZXMoY29sb3I9YW5ub3RhdGlvbiksIHNpemU9MC43LCBhbHBoYT0wLjMpICsKICBnZW9tX3Ntb290aCggbWV0aG9kPSJsb2VzcyIsc3Bhbj0wLjIpICsKICBmYWNldF9ncmlkKGRhdGF+Z2VuZSwgc2NhbGVzPSJmcmVlIikgKwogIHhsYWIoIlBzZXVkb3RpbWUgcmFuayIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgCn0KCnRmcyA8LSBjKCJKVU4iLCAiRk9TTDIiLCAiRk9TTDEiLCAiRk9TIikKCnBkZihwYXN0ZTAob3V0ZGlyLCAiVEZfcGxvdHMucGRmIiksIHdpZHRoID0gOCwgaGVpZ2h0ID0gNSkKZm9yICh0ZiBpbiB0Zi50b3B2YXIpIHsKICBwcmludChwbG90LnRmcyh0ZikpCn0KZGV2Lm9mZigpCgptYXAobGlzdCgiSlVOIiwgIkVMSzMiLCAiUlVOWDIiLCAiUkVMIiwgIkZPUyIsICJFVFY2IiwgIlRDRjMiKSwgfiBwbG90LnRmcygueCkgKyBnZ3NhdmUocGFzdGUwKG91dGRpciwgcGFzdGUwKCdURl9wbG90XycsLngsIi5wbmciKSksIHdpZHRoID0gOCwgaGVpZ2h0PTUpKQpgYGAKCgoKPCEtLSBgYGB7ciwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTd9IC0tPgo8IS0tIGJpbmRfcm93cyhhY2Nlc3MuZGYsIGdlbmV4LmRmKSAlPiUgLS0+CjwhLS0gICBmaWx0ZXIoZ2VuZSAlaW4lIGMoIlNQSTEiLCAiUlVOWDIiLCJSVU5YMyIsICdUQ0Y3TDInLCAiRTJGNCIpKSAlPiUgLS0+CjwhLS0gICAgIGZpbHRlcighc3RyX2RldGVjdChjZWxsLCAiXkFUQUNfIikgfCB0ZWNoPT0iQVRBQyIpICU+JSAtLT4KPCEtLSAgICMgZ3JvdXBfYnkodGVjaCwgZ2VuZSkgJT4lIC0tPgo8IS0tICAgIyBtdXRhdGUodmFsdWU9c2NhbGUodmFsdWUpKSAlPiUgLS0+CjwhLS0gICAjIHVuZ3JvdXAoKSAlPiUgLS0+CjwhLS0gICAjIGRyb3BfbmEoKSAlPiUgLS0+CjwhLS0gIyBmaWx0ZXIodGVjaD09IlJOQSIpICU+JSAtLT4KPCEtLSAgIGRyb3BfbmEoZHB0X2JpbikgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhkcHRfYmluLCB2YWx1ZSkpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KGFlcyhjb2xvcj1hbm5vdGF0aW9uKSwgYWxwaGE9MC4yKSArIC0tPgo8IS0tICAgZmFjZXRfZ3JpZCh0ZWNofmdlbmUsIHNjYWxlcyA9ICJmcmVlX3kiKSArIC0tPgo8IS0tICAgZ2VvbV9zbW9vdGgoKSAtLT4KCgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3IsIGZpZy53aWR0aD0xNCwgZmlnLmhlaWdodD03fSAtLT4KPCEtLSBiaW5kX3Jvd3MoYWNjZXNzLmRmLCBnZW5leC5kZikgJT4lIC0tPgo8IS0tICAgZmlsdGVyKGdlbmUgJWluJSBjKCJFTEszIiwgIkpVTkIiLCAiRk9TIikpICU+JSAtLT4KPCEtLSAgIGZpbHRlcighc3RyX2RldGVjdChjZWxsLCAiXkFUQUNfIikgfCB0ZWNoPT0iQVRBQyIpICU+JSAtLT4KPCEtLSAgICMgZ3JvdXBfYnkodGVjaCwgZ2VuZSkgJT4lIC0tPgo8IS0tICAgIyBtdXRhdGUodmFsdWU9c2NhbGUodmFsdWUpKSAlPiUgLS0+CjwhLS0gICAjIHVuZ3JvdXAoKSAlPiUgLS0+CjwhLS0gICBkcm9wX25hKGRwdF9iaW4pICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoZHB0X2JpbiwgdmFsdWUpKSArIC0tPgo8IS0tICAgZ2VvbV9wb2ludChhZXMoY29sb3I9YW5ub3RhdGlvbiksIGFscGhhPTAuMikgKyAtLT4KPCEtLSAgIGZhY2V0X2dyaWQodGVjaH5nZW5lLCBzY2FsZXMgPSAiZnJlZV95IikgKyAtLT4KPCEtLSAgIGdlb21fc21vb3RoKCkgKyAtLT4KPCEtLSAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9Y2VsbC50eXBlLnBhbCkgLS0+CgoKPCEtLSBgYGAgLS0+Cgo8IS0tICMjIFBzZXVkb3RpbWUgbGFnIGJldHdlZW4gRFAoUSkgaW4gYWNjZXNzaWJpbGl0eSBhbmQgZ2VuZSBleHByZXNzaW9uIC0tPgo8IS0tIFRoZSBEUCAoUSkgY2x1c3RlciBpbiB0aGUgQVRBQyBjZWxscyBpcyBzY29yZWQgd2l0aCBoaWdoIGNvbmZpZGVuY2UgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIGRwcS5jb2VtYmVkIDwtIGNvZW1iZWRbLHdoaWNoKGNvZW1iZWQkYW5ub3RhdGlvbj09IkRQIChRKSIpXSAtLT4KCjwhLS0gRmVhdHVyZVBsb3QoY29lbWJlZCwgZmVhdHVyZT0icHJlZGljdGlvbi5zY29yZS5tYXgiKSAtLT4KPCEtLSBEaW1QbG90KGNvZW1iZWQsIGdyb3VwLmJ5ID0iYW5ub3RhdGlvbiIsIHNwbGl0LmJ5ID0gInRlY2giKSAtLT4KCjwhLS0gYGBgIC0tPgoKPCEtLSBgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTZ9IC0tPgo8IS0tIGNjYS5vYmogPC0gdHJhbnNmZXIuYW5jaG9yc0BvYmplY3QubGlzdFtbMV1dIC0tPgo8IS0tIG5ldy5tZXRhZGF0YSA8LSBjb2VtYmVkQG1ldGEuZGF0YVssYygiYW5ub3RhdGlvbiIsICJ0ZWNoIiksIGRyb3A9Rl0gJT4lIHJvd25hbWVzX3RvX2NvbHVtbigpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShyb3duYW1lPWlmZWxzZShzdHJfZGV0ZWN0KHJvd25hbWUsICJeUk5BIiksIHN0cl9jKHJvd25hbWUsICJfcmVmZXJlbmNlIiksIHN0cl9jKHJvd25hbWUsICJfcXVlcnkiKSkpICU+JSAtLT4KPCEtLSAgIGNvbHVtbl90b19yb3duYW1lcygpIC0tPgo8IS0tIGNjYS5vYmogPC0gQWRkTWV0YURhdGEoY2NhLm9iaiwgbmV3Lm1ldGFkYXRhKSAtLT4KPCEtLSBjY2Eub2JqQG1ldGEuZGF0YSAtLT4KPCEtLSBEaW1QbG90KGNjYS5vYmosIGdyb3VwLmJ5PWMoImFubm90YXRpb24iLCJ0ZWNoIiksIHJlZHVjdGlvbiA9ICJjY2EiLCBkaW1zID0gMToyKSAtLT4KPCEtLSBEaW1QbG90KGNjYS5vYmosIGdyb3VwLmJ5PWMoImFubm90YXRpb24iLCJ0ZWNoIiksIHJlZHVjdGlvbiA9ICJjY2EiLCBkaW1zID0gMzo0KSAtLT4KPCEtLSBEaW1QbG90KGNjYS5vYmosIGdyb3VwLmJ5PWMoImFubm90YXRpb24iLCJ0ZWNoIiksIHJlZHVjdGlvbiA9ICJjY2EiLCBkaW1zID0gNTo2KSAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tIGBgYHtyLCBmaWcuaGVpZ2h0PTE4LCBmaWcud2lkdGg9MTh9IC0tPgo8IS0tIHRvcC5jYy5nZW5lcyA8LSBjY2Eub2JqQHJlZHVjdGlvbnMkY2NhLmwyQGZlYXR1cmUubG9hZGluZ3MgJT4lICAtLT4KPCEtLSAgIHJlc2hhcGUyOjptZWx0KHZhcm5hbWVzPWMoImdlbmUiLCAiQ0MiKSkgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkoQ0MpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShyYW5rPXJhbmsoYWJzKHZhbHVlKSkpICU+JSAtLT4KPCEtLSAgIHVuZ3JvdXAoKSAlPiUgLS0+CjwhLS0gICBmaWx0ZXIocmFuayA+IChtYXgocmFuayktMTApKSAlPiUgLS0+CjwhLS0gICBwdWxsKGdlbmUpICU+JSAtLT4KPCEtLSAgIHVuaXF1ZSgpIC0tPgoKPCEtLSBhdGFjLm1hdCA8LSBjb2VtYmVkQGFzc2F5cyRBVEFDQGRhdGEgLS0+CjwhLS0gcm5hLm1hdCA8LSBjb2VtYmVkQGFzc2F5cyRSTkFAZGF0YSAtLT4KCjwhLS0gYXRhYy5tYXRbZ2VuZS5vaSxdICU+JSAtLT4KPCEtLSAgIHsuWyx3aGljaChhcHBseSguLDIsIGZ1bmN0aW9uKHgpIHN1bSh4KSE9MCkpXX0gJT4lIC0tPgo8IS0tICAgcGhlYXRtYXA6OnBoZWF0bWFwKHNob3dfY29sbmFtZXM9RiwgY2x1c3RlcmluZ19kaXN0YW5jZV9yb3dzID0gImNvcnJlbGF0aW9uIiwgLS0+CjwhLS0gICAgICAgICAgICAgICAgICAgICAgIGFubm90YXRpb25fY29sID0gY29lbWJlZEBtZXRhLmRhdGFbLCJhbm5vdGF0aW9uIiwgZHJvcD1GXSAtLT4KPCEtLSAgICAgICAgICAgICAgICAgICAgICApIC0tPgo8IS0tIGBgYCAtLT4KPCEtLSBgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfSAtLT4KPCEtLSBkcHEuY2VsbHMgPC0gcm93bmFtZXMobmV3Lm1ldGFkYXRhW25ldy5tZXRhZGF0YSRhbm5vdGF0aW9uPT0iRFAgKFEpIixdKSAtLT4KPCEtLSBkcHEucXVlcnkuaXggPC0gd2hpY2godHJhbnNmZXIuYW5jaG9yc0BxdWVyeS5jZWxscyAlaW4lIGRwcS5jZWxscykgLS0+CjwhLS0gZHBxLnJlZi5peCA8LSB3aGljaCh0cmFuc2Zlci5hbmNob3JzQHJlZmVyZW5jZS5jZWxscyAlaW4lIGRwcS5jZWxscykgLS0+CjwhLS0gbmV3Lm1ldGFkYXRhICU+JSAtLT4KPCEtLSAgIHJvd25hbWVzX3RvX2NvbHVtbigpICU+JSAtLT4KPCEtLSAgIGZpbHRlcih0ZWNoPT0iQVRBQyIpIC0tPgo8IS0tIHRyYW5zZmVyLmFuY2hvcnNAYW5jaG9ycyAlPiUgLS0+CjwhLS0gICBhcy50aWJibGUoKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoY2VsbDE9dHJhbnNmZXIuYW5jaG9yc0ByZWZlcmVuY2UuY2VsbHNbY2VsbDFdKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoY2VsbDI9dHJhbnNmZXIuYW5jaG9yc0BxdWVyeS5jZWxsc1tjZWxsMl0pICU+JSAtLT4KPCEtLSAgIG11dGF0ZShhbm5vLmNlbGwxID0gbmV3Lm1ldGFkYXRhW2NlbGwxLCAnYW5ub3RhdGlvbiddKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoYW5uby5jZWxsMiA9IG5ldy5tZXRhZGF0YVtjZWxsMiwgJ2Fubm90YXRpb24nXSkgJT4lIC0tPgo8IS0tICAgIyBzcHJlYWQoY2VsbDIsIHNjb3JlKSAgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKHNjb3JlKSkgKyAtLT4KPCEtLSAgIGdlb21faGlzdG9ncmFtKCkgKyAtLT4KPCEtLSAgIHhsaW0oMCwxKSArIC0tPgo8IS0tICAgIyBnZW9tX3RpbGUoKSArIC0tPgo8IS0tICAgZmFjZXRfZ3JpZChhbm5vLmNlbGwxfmFubm8uY2VsbDIsIHNjYWxlcz0iZnJlZV95Iiwgc3BhY2U9ImZyZWUiLCBsYWJlbGxlciA9ICJsYWJlbF9ib3RoIikgIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gPCEtLSBzaG93IGFuY2hvciBtYXQgLS0+IC0tPgoKPCEtLSA8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTB9IC0tPiAtLT4KPCEtLSA8IS0tIGFuY2hvci5tYXQgPC0gdHJhbnNmZXIuYW5jaG9yc0BhbmNob3JzICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIGFzLnRpYmJsZSgpICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIG11dGF0ZShjZWxsMT10cmFuc2Zlci5hbmNob3JzQHJlZmVyZW5jZS5jZWxsc1tjZWxsMV0pICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIG11dGF0ZShjZWxsMj10cmFuc2Zlci5hbmNob3JzQHF1ZXJ5LmNlbGxzW2NlbGwyXSkgJT4lIC0tPiAtLT4KPCEtLSA8IS0tICAgc3ByZWFkKGNlbGwyLCBzY29yZSkgJT4lIC0tPiAtLT4KPCEtLSA8IS0tICAgY29sdW1uX3RvX3Jvd25hbWVzKCdjZWxsMScpICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIGFzLm1hdHJpeCgpIC0tPiAtLT4KCjwhLS0gPCEtLSBhbmNob3IubWF0ICU+JSAgLS0+IC0tPgo8IS0tIDwhLS0gICBpZmVsc2UoaXMubmEoLiksIDAsIC4pICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIHBoZWF0bWFwOjpwaGVhdG1hcChzaG93X3Jvd25hbWVzID0gRiwgc2hvd19jb2xuYW1lcyA9IEYsIC0tPiAtLT4KPCEtLSA8IS0tICAgICAgICAgICAgICAgICAgICAgIGFubm90YXRpb25fY29sID0gbmV3Lm1ldGFkYXRhWywnYW5ub3RhdGlvbicsIGRyb3A9Rl0sIC0tPiAtLT4KPCEtLSA8IS0tICAgICAgICAgICAgICAgICAgICAgIGFubm90YXRpb25fcm93ID0gbmV3Lm1ldGFkYXRhWywnYW5ub3RhdGlvbicsIGRyb3A9Rl0sIC0tPiAtLT4KPCEtLSA8IS0tICAgICAgICAgICAgICAgICAgICAgIGFubm90YXRpb25fY29sb3JzID0gbGlzdChhbm5vdGF0aW9uPWNlbGwudHlwZS5wYWwpKSAtLT4gLS0+CjwhLS0gPCEtLSBgYGAgLS0+IC0tPgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gcHJlZC5zY29yZXMgPC0gY29sbmFtZXMoY29lbWJlZEBtZXRhLmRhdGEpICU+JSBzdHJfc3Vic2V0KCJwcmVkaWN0aW9uLnNjb3JlIikgIC0tPgo8IS0tIGNvZW1iZWRAbWV0YS5kYXRhICU+JSAtLT4KPCEtLSAgIGZpbHRlcih0ZWNoPT0iQVRBQyIpICU+JSAtLT4KPCEtLSAgIHNlbGVjdChjKCJhbm5vdGF0aW9uIiwgcHJlZC5zY29yZXMpKSAlPiUgLS0+CjwhLS0gICBwaXZvdF9sb25nZXIoY29scz0tYW5ub3RhdGlvbiwgbmFtZXNfdG8gPSAiY2xhc3MiKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoY2xhc3M9c3RyX3JlbW92ZShjbGFzcywgInByZWRpY3Rpb24uc2NvcmUuIikpICU+JSAtLT4KPCEtLSAgIGZpbHRlcihjbGFzcyE9Im1heCIpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoYW5ub3RhdGlvbiwgdmFsdWUsIGZpbGw9Y2xhc3MpKSArIC0tPgo8IS0tICAgZ2VvbV9ib3hwbG90KCkgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSBgYGB7ciwgZmlnLndpZHRoPTEwfSAtLT4KPCEtLSBEZWZhdWx0QXNzYXkoZHBxLmNvZW1iZWQpIDwtICJSTkEiIC0tPgo8IS0tIGRwcS5jb2VtYmVkIDwtIFNjYWxlRGF0YShkcHEuY29lbWJlZCwgZmVhdHVyZXMgPSBpbnRlZ3JhdGVfZmVhdHVyZXNfdW5pb24pIC0tPgo8IS0tIGRwcS5jb2VtYmVkIDwtIFJ1blBDQShkcHEuY29lbWJlZCwgZmVhdHVyZXMgPSBpbnRlZ3JhdGVfZmVhdHVyZXNfdW5pb24pIC0tPgoKPCEtLSBwbG90X2dyaWQoIC0tPgo8IS0tICAgRGltUGxvdChkcHEuY29lbWJlZCwgZ3JvdXAuYnkgPSBjKCJ0ZWNoIiksIHJlZHVjdGlvbiA9ICJwY2EiKSAsIC0tPgo8IS0tICAgRmVhdHVyZVBsb3QoZHBxLmNvZW1iZWQsIGZlYXR1cmU9ImRwdF9wc2V1ZG90aW1lIiwgcmVkdWN0aW9uID0gInBjYSIpICsgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgIC0tPgo8IS0tICAgKSAtLT4KPCEtLSBgYGAgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIHRvcFBDMS5kZiA8LSBkcHEuY29lbWJlZEByZWR1Y3Rpb25zJHBjYUBmZWF0dXJlLmxvYWRpbmdzWywxLCBkcm9wPUZdICU+JSAtLT4KPCEtLSAgIGFzLnRpYmJsZShyb3duYW1lcz0iZ2VuZSIpICU+JSAtLT4KPCEtLSAgICMgYXJyYW5nZShQQ18xKSAtLT4KPCEtLSAgIG11dGF0ZShyYW5rPSBkZW5zZV9yYW5rKFBDXzEpKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUobGFiZWw9aWZlbHNlKHJhbmsgPiAobigpLTEwKSB8IHJhbmsgPCAoMTApICwgZ2VuZSxOQSkpICAtLT4KCjwhLS0gdG9wUEMxLmRmICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMocmFuaywgUENfMSkpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KCkrIC0tPgo8IS0tICAgZ2dyZXBlbDo6Z2VvbV90ZXh0X3JlcGVsKGRhdGE9LiAlPiUgZmlsdGVyKCFpcy5uYShsYWJlbCkpLCBhZXMobGFiZWw9bGFiZWwpKSAtLT4KPCEtLSBgYGAgLS0+CjwhLS0gYGBge3IsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTh9IC0tPgo8IS0tIEZlYXR1cmVQbG90KGRwcS5jb2VtYmVkLCBmZWF0dXJlPXVuaXF1ZSh0b3BQQzEuZGYkbGFiZWwpWzE6Nl0sIHJlZHVjdGlvbiA9ICJ1bWFwIikgIC0tPgo8IS0tICAgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSBgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTR9IC0tPgo8IS0tIGNjYS5vYmpAcmVkdWN0aW9ucyRjY2FAZmVhdHVyZS5sb2FkaW5ncyAlPiUgLS0+CjwhLS0gICByZXNoYXBlMiAtLT4KPCEtLSAgIHBoZWF0bWFwOjpwaGVhdG1hcCgpIC0tPgo8IS0tIERpbUhlYXRtYXAoY2NhLm9iaiwgcmVkdWN0aW9uID0gImNjYSIsIGFzc2F5cyA9ICJBVEFDIikgLS0+CjwhLS0gRmVhdHVyZVBsb3QoY2NhLm9iaiwgcmVkdWN0aW9uPSJjY2EiLCBmZWF0dXJlPWdlbmUub2kpIC0tPgoKPCEtLSBgYGAgLS0+CgoKPCEtLSA8IS0tIERpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcyBiZXR3ZWVuIFJOQSBhbmQgQVRBQyBEUChRKSBjZWxscyAtLT4gLS0+CjwhLS0gPCEtLSBgYGB7cn0gLS0+IC0tPgo8IS0tIDwhLS0gVmFyaWFibGVGZWF0dXJlcyhjb2VtYmVkKSA8LSBpbnRlZ3JhdGVfZmVhdHVyZXNfdW5pb24gLS0+IC0tPgo8IS0tIDwhLS0gZHBxLmRpZmYgPC0gRmluZE1hcmtlcnMoZHBxLmNvZW1iZWQsIGZlYXR1cmVzPVZhcmlhYmxlRmVhdHVyZXMoY29lbWJlZCksICAtLT4gLS0+CjwhLS0gPCEtLSAgICAgICAgICAgICAgICAgICAgICAgICBncm91cC5ieSA9ICJ0ZWNoIiwgaWRlbnQuMSA9ICJSTkEiLCBpZGVudC4yID0gIkFUQUMiKSAtLT4gLS0+Cgo8IS0tIDwhLS0gdG9wLmRpZmYgPC0gcm93bmFtZXMoZHBxLmRpZmZbMToxMCxdKSAtLT4gLS0+CjwhLS0gPCEtLSBGZWF0dXJlUGxvdChjb2VtYmVkLCBmZWF0dXJlcyA9IHRvcC5kaWZmWzE6M10sIHNwbGl0LmJ5ID0gInRlY2giLCBjb2xzID0gdmlyaWRpczo6dmlyaWRpcyhuPTEwMCksIHNsb3QgPSAic2NhbGUuZGF0YSIsIG1heC5jdXRvZmYgPSAxMCkgKyBnZ3RpdGxlKCJTdGFnZSBNYXJrZXJzIikgLS0+IC0tPgo8IS0tIDwhLS0gYGBgIC0tPiAtLT4KPCEtLSA8IS0tIGBgYHtyfSAtLT4gLS0+Cgo8IS0tIDwhLS0gVmxuUGxvdChjb2VtYmVkWywgY29lbWJlZCR0ZWNoPT0iUk5BIl0sIGZlYXR1cmVzID0gdG9wLmRpZmZbMV0sIGdyb3VwLmJ5ID0gImFubm90YXRpb24iLCBwdC5zaXplID0gMC4xLCBzcGxpdC5ieSA9ICd0ZWNoJykgLS0+IC0tPgo8IS0tIDwhLS0gVmxuUGxvdChjb2VtYmVkLCBmZWF0dXJlcyA9IGMoIkFRUDMiLCAiVFJCQzIiKSwgZ3JvdXAuYnkgPSAidGVjaCIpIC0tPiAtLT4KPCEtLSA8IS0tIGBgYCAtLT4gLS0+Cgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTJ9IC0tPgo8IS0tIGF0YWMubWF0IDwtIGNvZW1iZWRAYXNzYXlzJEFUQUNAZGF0YSAtLT4KPCEtLSBybmEubWF0IDwtIGNvZW1iZWRAYXNzYXlzJFJOQUBkYXRhIC0tPgoKPCEtLSBnZW5lLm9pIDwtIHVuaXF1ZSh0b3BQQzEuZGYkbGFiZWwpICU+JSBzdHJfc3Vic2V0KCJUUkFWIikgLS0+CjwhLS0gZ2VuZS5vaSAtLT4KPCEtLSBhdGFjLmRmIDwtIGF0YWMubWF0W2dlbmUub2ksLCBkcm9wPUZdICU+JSAtLT4KPCEtLSAgIGFzLm1hdHJpeCgpICU+JSAtLT4KPCEtLSAgIHJlc2hhcGUyOjptZWx0KHZhcm5hbWVzPWMoImdlbmUiLCAiY2VsbCIpKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUodGVjaD0iQVRBQyIpICU+JSAtLT4KPCEtLSAgIGxlZnRfam9pbihkcHQuZGYsIGJ5PWMoJ2NlbGwnLCAidGVjaCIpKSAgLS0+CjwhLS0gIyAlPiUgLS0+CjwhLS0gIyAgIGdyb3VwX2J5KHRlY2gsIGRwdF9iaW4sIGdlbmUpICU+JSAtLT4KPCEtLSAjICAgc3VtbWFyaXNlKGZyYWM9c3VtKHZhbHVlIT0wKS9uKCkpIC0tPgoKPCEtLSBybmEuZGYgPC0gcm5hLm1hdFtnZW5lLm9pLCwgZHJvcD1GXSAlPiUgLS0+CjwhLS0gICBhcy5tYXRyaXgoKSAlPiUgLS0+CjwhLS0gICByZXNoYXBlMjo6bWVsdCh2YXJuYW1lcz1jKCJnZW5lIiwgImNlbGwiKSkgJT4lIC0tPgo8IS0tICAgZmlsdGVyKHN0cl9kZXRlY3QoY2VsbCwgIlJOQV8iKSkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKHRlY2g9IlJOQSIpICU+JSAtLT4KPCEtLSAgIGxlZnRfam9pbihkcHQuZGYsIGJ5PWMoJ2NlbGwnLCAidGVjaCIpKSAtLT4KCgo8IS0tIGFjYy5wbCA8LSBhdGFjLmRmICU+JSAtLT4KPCEtLSAgIGRyb3BfbmEoZHB0X2JpbikgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhkcHRfYmluLCBmaWxsPWFzLmZhY3Rvcih2YWx1ZSkpKSArIC0tPgo8IS0tICAgZ2VvbV9iYXIoKSAgLS0+CjwhLS0gICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Y2VsbC50eXBlLnBhbCwgbmEudmFsdWU9ImdyZXk1MCIpIC0tPgo8IS0tIGFjYy5wbCAtLT4KPCEtLSBleC5wbCA8LSBybmEuZGYgJT4lIC0tPgo8IS0tICAgZmlsdGVyKGdlbmU9PWdlbmUub2kpICU+JSAtLT4KPCEtLSAgICMgbXV0YXRlKHZhbHVlPWlmZWxzZSh2YWx1ZT4wLDEsMCkpICU+JSAtLT4KPCEtLSAgICMgZmlsdGVyKHZhbHVlIT0wKSAlPiUgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKGRwdF9iaW4sIHZhbHVlKSkgKyAtLT4KPCEtLSAgICMgZ2VvbV92aW9saW4oYWxwaGE9MC4yKSArIC0tPgo8IS0tICAgIyBnZW9tX3BvaW50KCkgLS0+CjwhLS0gICAjIHNjYWxlX2ZpbGxfdmlyaWRpc19kKCkgLS0+CjwhLS0gICAjIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jZWxsLnR5cGUucGFsKSAtLT4KPCEtLSAgIGdlb21faml0dGVyKGFscGhhPTAuNSwgc2l6ZT0wLjUpICsgLS0+CjwhLS0gICBnZW9tX3Ntb290aCgpICAtLT4KPCEtLSAgIHhsaW0oMCw1MCkgLS0+CgoKPCEtLSBwbG90X2dyaWQoYWNjLnBsLCBleC5wbCwgbmNvbD0xLCBucm93PTIsIGFsaWduID0gInYiLCBheGlzLj0ibCIpIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIGF0YWMuZGYgJT4lIC0tPgo8IS0tICAgZHJvcF9uYShkcHRfcHNldWRvdGltZSkgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkoIGdlbmUsIGRwdF9iaW4pICU+JSAtLT4KPCEtLSAgICMgc3VtbWFyaXNlKGZyYWM9c3VtKHZhbHVlKS9uKCkpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoZHB0X2JpbiwgdmFsdWUsIGNvbG9yPWdlbmUpKSArIC0tPgo8IS0tICAgIyBnZW9tX3BvaW50KCkgKyAtLT4KPCEtLSAgIGdlb21fc21vb3RoKCkgLS0+CjwhLS0gYGBgIC0tPgoKLS0tCgoKCgoKCgo=